Commit 896f6f72 by Ivan Lan

Add pasting type logic

parent 6402d2ab
......@@ -4,7 +4,6 @@ class CreateActsAsPastingPastings < ActiveRecord::Migration[5.2]
t.references :pasteable, polymorphic: true, index: { name: 'acts_as_pasting_pasteable' }
t.references :pasted, polymorphic: true, index: { name: 'acts_as_pasting_pasted' }
t.string :type, index: true
t.string :pasting_type
t.timestamps
end
......
module ActsAsPasting
module Pasted
extend ActiveSupport::Concern
included do
has_many :pastings, as: :pasted, class_name: 'ActsAsPasting::Pasting'
# ary => [[klass, id], [klass, id]]
# ary => [obj, obj]
scope :pasted_with_any, ->(ary) {
ary = parsed_condition_ary(ary)
ary.map { |klass, id| joins(:pastings).where(
'acts_as_pasting_pastings.pasteable_type = ? AND acts_as_pasting_pastings.pasteable_id = ?',
klass.constantize.base_class.name, id
)
}.reduce(:or)&.distinct || self.none
}
scope :pasted_with_all, ->(ary) {
ary = parsed_condition_ary(ary)
ids = ary.map do |klass, id|
joins(:pastings).where(pastings: { pasteable_type: klass.constantize.base_class.name, pasteable_id: id }).pluck(:id)
end.reduce(:&)
where(id: ids)
}
after_create :save_paste_list
def paste_list
pastings.reload.map(&:pasteable)
end
def paste_list_for klass_name
klass_name.constantize.where(
id: pastings.where(pasteable_type: klass_name.constantize.base_class.name).pluck(:pasteable_id)
)
end
def paste_list_names
paste_list.map(&:name)
end
module Pasted
extend ActiveSupport::Concern
# 带 prefix 的 pasted_with,独立于不带 prefix 的之外
included do
has_many :pastings, as: :pasted
# ary => [[klass, id], [klass, id]]
# ary => [obj, obj]
scope :pasted_with_any, ->(ary, prefix: '') {
ary = parsed_condition_ary(ary)
ids = ary.map do |klass, id|
joins(:pastings).where(pastings: {
pasteable_type: klass.constantize.base_class.name, pasteable_id: id, type: prefix
}).pluck(:id)
end.reduce(:|)
where(id: ids)
}
scope :pasted_with_all, ->(ary, prefix: '') {
ary = parsed_condition_ary(ary)
ids = ary.map do |klass, id|
joins(:pastings).where(pastings: {
pasteable_type: klass.constantize.base_class.name, pasteable_id: id, type: prefix
}).pluck(:id)
end.reduce(:&)
where(id: ids)
}
after_create :save_paste_list
# 不包含 带 prefix 的 pasted_with
def paste_list
pastings.where(type: '').reload.map(&:pasteable).uniq
end
def paste_list_add obj
pastings.create!(pasted: self, pasteable: obj)
end
# def paste_list_for klass_name
# klass_name.constantize.where(
# id: pastings.where(pasteable_type: klass_name.constantize.base_class.name).pluck(:pasteable_id)
# )
# end
def paste_list_remove obj
pastings.where(pasted: self, pasteable: obj).destroy_all
end
# ary => [[klass, id], [klass, id]]
# ary => [obj, obj]
def paste_list= ary
ary = self.class.parsed_condition_ary(ary)
exist_ary = pastings.where(pasted: self).pluck(:pasteable_type, :pasteable_id)
add_ary = ary - exist_ary
remove_ary = exist_ary - ary
if new_record?
@paste_list = ary
else
self.class.transaction do
remove_ary.each { |klass, id| pastings.where(
pasted: self,
pasteable_type: klass,
pasteable_id: id
).destroy_all
}
add_ary.each { |klass, id| pastings.create!(
pasted: self,
pasteable_type: klass,
pasteable_id: id
)
}
end
end
end
def paste_list_names
paste_list.map(&:name)
end
def method_missing name, *arg, &block
klass_downcase = /^paste_(.+)_list$/.match(name)&.[](1)
if klass_downcase
downcase_all_combination(klass_downcase).each do |str|
return paste_list_for(str) if correct_class_name?(str)
end
end
super(name, *arg, &block)
end
def paste_list_add obj
pastings.where(type: '').create!(pasted: self, pasteable: obj)
end
private
def paste_list_remove obj
pastings.where(type: '').where(pasted: self, pasteable: obj).destroy_all
end
def save_paste_list
if @paste_list
self.paste_list = @paste_list
# ary => [[klass, id], [klass, id]]
# ary => [obj, obj]
# 不包含 带 prefix 的 pasted_with
def paste_list= ary
ary = self.class.parsed_condition_ary(ary)
exist_ary = pastings.where(pasted: self).pluck(:pasteable_type, :pasteable_id)
add_ary = ary - exist_ary
remove_ary = exist_ary - ary
if new_record?
@paste_list = ary
else
self.class.transaction do
remove_ary.each { |klass, id| pastings.where(
type: '',
pasted: self,
pasteable_type: klass,
pasteable_id: id
).destroy_all
}
add_ary.each { |klass, id| pastings.create!(
type: '',
pasted: self,
pasteable_type: klass,
pasteable_id: id
)
}
end
end
end
def correct_class_name? name
ActiveRecord::Base === name.safe_constantize.try(:new)
# def method_missing name, *arg, &block
# klass_downcase = /^paste_(.+)_list$/.match(name)&.[](1)
# if klass_downcase
# downcase_all_combination(klass_downcase).each do |str|
# return paste_list_for(str) if correct_class_name?(str)
# end
# end
# super(name, *arg, &block)
# end
private
def save_paste_list
if @paste_list
self.paste_list = @paste_list
end
end
def downcase_all_combination str
downcase_all_combination_ary(str).reverse.map { |x| (String === x ? x : x.flatten.join).classify }
end
def correct_class_name? name
ActiveRecord::Base === name.safe_constantize.try(:new)
end
def downcase_all_combination_ary str
items = String === str ? str.split('_') : str
return items if items.length == 1
l = downcase_all_combination_ary(items[1..-1])
result = []
l.each do |i|
result.push(items[0, 1] << '/' << i)
result.push(items[0, 1] << '_' << i)
end
result
end
def downcase_all_combination str
downcase_all_combination_ary(str).reverse.map { |x| (String === x ? x : x.flatten.join).classify }
end
module ClassMethods
def pasted_with *klasses
klasses.each do |klass|
downcase = klass.name.underscore.gsub('/', '_')
base_class_name = klass.base_class.name
class_eval <<-RUBY, __FILE__, __LINE__ + 1
after_create :save_paste_#{downcase}_list
def downcase_all_combination_ary str
items = String === str ? str.split('_') : str
return items if items.length == 1
l = downcase_all_combination_ary(items[1..-1])
result = []
l.each do |i|
result.push(items[0, 1] << '/' << i)
result.push(items[0, 1] << '_' << i)
end
result
end
end
def paste_#{downcase}_list
paste_list_for("#{klass}")
end
module ClassMethods
def pasted_with *klasses, prefix: nil
klasses.each do |klass|
downcase = klass.name.underscore.gsub('/', '_')
downcase = "#{prefix}#{downcase}" if prefix
type_value = prefix ? prefix : nil
base_class_name = klass.base_class.name
class_eval <<-RUBY, __FILE__, __LINE__ + 1
after_create :save_paste_#{downcase}_list
def paste_#{downcase}_list
#{klass}.where(
id: pastings.where(type: "#{type_value}", pasteable_type: #{klass}.base_class.name).pluck(:pasteable_id)
)
end
def paste_#{downcase}_ids= ids
ary = ids.map { |id| ["#{base_class_name}", id] }
self.paste_#{downcase}_list = ary
end
def paste_#{downcase}_ids= ids
ary = ids.map { |id| ["#{base_class_name}", id] }
self.paste_#{downcase}_list = ary
end
def paste_#{downcase}_list= ary, run_save: true
ary = self.class.parsed_condition_ary(ary).select { |a|
a.first == "#{base_class_name}"
}
if new_record?
@paste_#{downcase}_list = ary
else
exist_ary = pastings.where(pasted: self, pasteable_type: "#{base_class_name}").pluck(:pasteable_type, :pasteable_id)
add_ary = ary - exist_ary
remove_ary = exist_ary - ary
self.class.transaction do
remove_ary.each { |klass, id| pastings.where(
pasted: self,
pasteable_type: klass,
pasteable_id: id
).destroy_all
}
add_ary.each { |klass, id| pastings.create!(
pasted: self,
pasteable_type: klass,
pasteable_id: id
)
}
end
def paste_#{downcase}_list= ary, run_save: true
ary = self.class.parsed_condition_ary(ary).select { |a|
a.first == "#{base_class_name}"
}
if new_record?
@paste_#{downcase}_list = ary
else
exist_ary = pastings.where(
type: "#{type_value}", pasted: self, pasteable_type: "#{base_class_name}"
).pluck(:pasteable_type, :pasteable_id)
add_ary = ary - exist_ary
remove_ary = exist_ary - ary
self.class.transaction do
remove_ary.each { |klass, id| pastings.where(
type: "#{type_value}",
pasted: self,
pasteable_type: klass,
pasteable_id: id
).destroy_all
}
add_ary.each { |klass, id| pastings.create!(
type: "#{type_value}",
pasted: self,
pasteable_type: klass,
pasteable_id: id
)
}
end
end
end
private
private
def save_paste_#{downcase}_list
if @paste_#{downcase}_list
self.paste_#{downcase}_list = @paste_#{downcase}_list
end
def save_paste_#{downcase}_list
if @paste_#{downcase}_list
self.paste_#{downcase}_list = @paste_#{downcase}_list
end
RUBY
end
end
RUBY
end
end
def parsed_condition_ary ary
case ary.first
when ActiveRecord::Base
ary.map { |obj| [obj.class.base_class.name, obj.id] }
when Hash
ary.map { |h| [h[:paste_type].constantize.base_class.name, h[:paste_id]] }
else
ary.map { |a| [a.first.constantize.base_class.name, a.last] }
end
def parsed_condition_ary ary
ary = ary.compact
return [] unless ary && ary.first
case ary.first
when ActiveRecord::Base
ary.map { |obj| [obj.class.base_class.name, obj.id] }
when Hash
ary.map { |h| [h[:pasted_type].constantize.base_class.name, h[:pasted_id]] }
else
ary.map { |a| [a.first.constantize.base_class.name, a.last] }
end
end
def acts_as_pasted associations, options={}
has_many associations, through: :pastings, source: :pasteable, **options
end
def acts_as_pasted associations, options={}
has_many associations, through: :pastings, source: :pasteable, **options
end
end
module Association
def acts_as_pasted associations, options={}
has_many :pastings, as: :pasteable
has_many associations, through: :pastings, source: :pasted, **options
end
module Association
def acts_as_pasted associations, options={}
has_many :pastings, as: :pasteable
has_many associations, through: :pastings, source: :pasted, **options
end
end
end
......@@ -20,6 +20,8 @@
#
module ActsAsPasting
class Pasting < ApplicationRecord
self.inheritance_column = :_type_disabled
belongs_to :pasteable, polymorphic: true
belongs_to :pasted, polymorphic: true
end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment