acts_as_pasting 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8f97c3548969662c03b64aeef908576c64ad6fd4b8b4a836900e8df04eeef484
4
+ data.tar.gz: 2b0c49b1d60e321ba3fceac35d708bab23859678455f04512adcb098d5fd3b83
5
+ SHA512:
6
+ metadata.gz: faed852669a7d327f5aba041331a02b97823e18e5d7c78b1d215f952f68e5e2404ee70da7cbc619fcd339d244ad2f0b116c44430a0764ac2e4156664ed862527
7
+ data.tar.gz: a4e288d1a1f1e4d39b16c8e618b2e07593459cc53d5f80a186820f8a8578df666a1606db7cb1e7ce56b70c8a9c21dcb15914ca6f14d1f2ece2722e30abcdd884
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in whaleback.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # Whaleback
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/whaleback`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Usage
8
+
9
+ Copy migrations to your project
10
+
11
+ $ rails g acts_as_pasting:migrations
12
+
13
+ TODO: Write usage instructions here
14
+
15
+ ```ruby
16
+ class Teacher < ApplicationRecord
17
+ include ActsAsPasting::Pasted
18
+ pasted_with ::Duty
19
+ acts_as_pasted :duties, source_type: 'Duty'
20
+ end
21
+
22
+ class Duty < ApplicationRecord
23
+ include ActsAsPasting::Pastable
24
+ acts_as_pastable :teachers, source_type: 'Teacher'
25
+ end
26
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "whaleback"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,11 @@
1
+ class CreateActsAsPastingPastings < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :acts_as_pasting_pastings do |t|
4
+ t.references :pasteable, polymorphic: true, index: { name: 'acts_as_pasting_pasteable' }
5
+ t.references :pasted, polymorphic: true, index: { name: 'acts_as_pasting_pasted' }
6
+ t.string :type, index: true
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require "acts_as_pasting/version"
2
+
3
+ module ActsAsPasting
4
+ require 'acts_as_pasting/errors'
5
+ autoload :Pasted, 'acts_as_pasting/pasted'
6
+ autoload :Pastable, 'acts_as_pasting/pastable'
7
+ autoload :Pasting, 'acts_as_pasting/pasting'
8
+
9
+ def self.table_name_prefix
10
+ 'acts_as_pasting_'
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module ActsAsPasting
2
+ class Error < StandardError
3
+ end
4
+
5
+ module Errors
6
+ class BaseError < ActsAsPasting::Error
7
+ attr_accessor :message, :code, :status
8
+ def initialize message, code, status
9
+ super(message)
10
+ @message, @code, @status = message, code, status.to_i
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ module ActsAsPasting
2
+ module Pastable
3
+ extend ActiveSupport::Concern
4
+
5
+ # 带 prefix 的 pasted_with,独立于不带 prefix 的之外
6
+ included do
7
+ has_many :pastable_pastings, as: :pasteable, class_name: 'ActsAsPasting::Pasting'
8
+ has_many :hosts, through: :pastable_pastings
9
+
10
+ # ary => [[klass, id], [klass, id]]
11
+ # ary => [obj, obj]
12
+ scope :pastable_with_any, ->(ary, prefix: '') {
13
+ ary = parsed_condition_ary(ary)
14
+ arel = Arel::Table.new(:acts_as_pasting_pastings, as: "acts_as_pasting_pastings_#{SecureRandom.hex(10)}")
15
+ source_arel = arel_table
16
+ join_condition = ary.group_by(&:first).map do |(klass, value)|
17
+ ids = value.map(&:last)
18
+ arel[:pasted_type].eq(klass.constantize.base_class.name).
19
+ and(arel[:pasted_id].in(ids)).
20
+ and(arel[:pasteable_id].eq(source_arel[:id])).
21
+ and(arel[:type].eq(prefix))
22
+ end.reduce(:or)
23
+ joins(arel.join(arel).on(join_condition).join_sources).distinct
24
+ }
25
+
26
+ scope :pastable_with_all, ->(ary, prefix: '') {
27
+ ary = parsed_condition_ary(ary)
28
+ ary.reduce(where(nil)) do |association, value|
29
+ association.pastable_with_any([value], prefix: prefix)
30
+ end
31
+ }
32
+ end
33
+
34
+ module ClassMethods
35
+ def acts_as_pastable associations, prefix: '', **options
36
+ pasting_name = prefix.blank? ? "#{associations}_pastings".to_sym : [associations, prefix, 'pastings'].join('_').to_sym
37
+ has_many(pasting_name, -> { where(type: prefix) }, as: :pasteable, class_name: 'ActsAsPasting::Pasting')
38
+ has_many(associations, -> { distinct }, through: pasting_name, source: :pasted, **options)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,223 @@
1
+ module ActsAsPasting
2
+ module Pasted
3
+ extend ActiveSupport::Concern
4
+
5
+ # 带 prefix 的 pasted_with,独立于不带 prefix 的之外
6
+ included do
7
+ has_many :pastings, as: :pasted, class_name: 'ActsAsPasting::Pasting'
8
+ # ary => [[klass, id], [klass, id]]
9
+ # ary => [obj, obj]
10
+ scope :pasted_with_any, ->(ary, prefix: '') {
11
+ ary = parsed_condition_ary(ary)
12
+ return none unless ary.first
13
+ arel = Arel::Table.new(:acts_as_pasting_pastings, as: "acts_as_pasting_pastings_#{SecureRandom.hex(10)}")
14
+ source_arel = arel_table
15
+ join_condition = ary.group_by(&:first).map do |(klass, value)|
16
+ ids = value.map(&:last)
17
+ arel[:pasteable_type].eq(klass.constantize.base_class.name).
18
+ and(arel[:pasteable_id].in(ids)).
19
+ and(arel[:pasted_id].eq(source_arel[:id])).
20
+ and(arel[:type].eq(prefix))
21
+ end.reduce(:or)
22
+ joins(arel.join(arel).on(join_condition).join_sources).distinct
23
+ }
24
+
25
+ scope :pasted_with_all, ->(ary, prefix: '') {
26
+ ary = parsed_condition_ary(ary)
27
+ ary.reduce(where(nil)) do |association, value|
28
+ association.pasted_with_any([value], prefix: prefix)
29
+ end
30
+ }
31
+
32
+ after_create :save_paste_list
33
+
34
+ # 不包含 带 prefix 的 pasted_with
35
+ def paste_list
36
+ pastings.where(type: '').reload.map(&:pasteable).uniq
37
+ end
38
+
39
+ # def paste_list_for klass_name
40
+ # klass_name.constantize.where(
41
+ # id: pastings.where(pasteable_type: klass_name.constantize.base_class.name).pluck(:pasteable_id)
42
+ # )
43
+ # end
44
+
45
+ def paste_list_names
46
+ paste_list.map(&:name)
47
+ end
48
+
49
+ def paste_list_add obj
50
+ pastings.where(type: '').create!(pasted: self, pasteable: obj)
51
+ end
52
+
53
+ def paste_list_remove obj
54
+ pastings.where(type: '').where(pasted: self, pasteable: obj).destroy_all
55
+ end
56
+
57
+ # ary => [[klass, id], [klass, id]]
58
+ # ary => [obj, obj]
59
+ # 不包含 带 prefix 的 pasted_with
60
+ def paste_list= ary
61
+ ary = self.class.parsed_condition_ary(ary)
62
+ exist_ary = pastings.where(pasted: self).pluck(:pasteable_type, :pasteable_id)
63
+ add_ary = ary - exist_ary
64
+ remove_ary = exist_ary - ary
65
+ if new_record?
66
+ @paste_list = ary
67
+ else
68
+ self.class.transaction do
69
+ remove_ary.each { |klass, id| pastings.where(
70
+ type: '',
71
+ pasted: self,
72
+ pasteable_type: klass,
73
+ pasteable_id: id
74
+ ).destroy_all
75
+ }
76
+ add_ary.each { |klass, id| pastings.create!(
77
+ type: '',
78
+ pasted: self,
79
+ pasteable_type: klass,
80
+ pasteable_id: id
81
+ )
82
+ }
83
+ end
84
+ end
85
+ end
86
+
87
+ # def method_missing name, *arg, &block
88
+ # klass_downcase = /^paste_(.+)_list$/.match(name)&.[](1)
89
+
90
+ # if klass_downcase
91
+ # downcase_all_combination(klass_downcase).each do |str|
92
+ # return paste_list_for(str) if correct_class_name?(str)
93
+ # end
94
+ # end
95
+ # super(name, *arg, &block)
96
+ # end
97
+
98
+ private
99
+
100
+ def save_paste_list
101
+ if @paste_list
102
+ self.paste_list = @paste_list
103
+ end
104
+ end
105
+
106
+ def correct_class_name? name
107
+ ActiveRecord::Base === name.safe_constantize.try(:new)
108
+ end
109
+
110
+ def downcase_all_combination str
111
+ downcase_all_combination_ary(str).reverse.map { |x| (String === x ? x : x.flatten.join).classify }
112
+ end
113
+
114
+ def downcase_all_combination_ary str
115
+ items = String === str ? str.split('_') : str
116
+ return items if items.length == 1
117
+ l = downcase_all_combination_ary(items[1..-1])
118
+ result = []
119
+ l.each do |i|
120
+ result.push(items[0, 1] << '/' << i)
121
+ result.push(items[0, 1] << '_' << i)
122
+ end
123
+ result
124
+ end
125
+ end
126
+
127
+ module ClassMethods
128
+ def pasted_with *klasses, prefix: nil
129
+ klasses.each do |klass|
130
+ downcase = klass.name.underscore.gsub('/', '_')
131
+ downcase = "#{prefix}#{downcase}" if prefix
132
+ type_value = prefix ? prefix : nil
133
+ base_class_name = klass.base_class.name
134
+
135
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
136
+ after_create :save_paste_#{downcase}_list
137
+
138
+ def paste_#{downcase}_list
139
+ #{klass}.where(
140
+ id: pastings.where(type: "#{type_value}", pasteable_type: #{klass}.base_class.name).pluck(:pasteable_id)
141
+ )
142
+ end
143
+
144
+ def paste_#{downcase}_ids= ids
145
+ ary = ids.map { |id| ["#{base_class_name}", id] }
146
+ self.paste_#{downcase}_list = ary
147
+ end
148
+
149
+ def paste_#{downcase}_ids
150
+ pastings.where(type: "#{type_value}", pasteable_type: #{klass}.base_class.name).pluck(:pasteable_id)
151
+ end
152
+
153
+ def paste_#{downcase}_list= ary, run_save: true
154
+ ary = self.class.parsed_condition_ary(ary).select { |a|
155
+ a.first == "#{base_class_name}"
156
+ }
157
+ if new_record?
158
+ @paste_#{downcase}_list = ary
159
+ else
160
+ exist_ary = pastings.where(
161
+ type: "#{type_value}", pasted: self, pasteable_type: "#{base_class_name}"
162
+ ).pluck(:pasteable_type, :pasteable_id)
163
+ add_ary = ary - exist_ary
164
+ remove_ary = exist_ary - ary
165
+
166
+ self.class.transaction do
167
+ remove_ary.each { |klass, id| pastings.where(
168
+ type: "#{type_value}",
169
+ pasted: self,
170
+ pasteable_type: klass,
171
+ pasteable_id: id
172
+ ).destroy_all
173
+ }
174
+ add_ary.each { |klass, id| pastings.create!(
175
+ type: "#{type_value}",
176
+ pasted: self,
177
+ pasteable_type: klass,
178
+ pasteable_id: id
179
+ )
180
+ }
181
+ end
182
+ end
183
+ end
184
+
185
+ private
186
+
187
+ def save_paste_#{downcase}_list
188
+ if @paste_#{downcase}_list
189
+ self.paste_#{downcase}_list = @paste_#{downcase}_list
190
+ end
191
+ end
192
+ RUBY
193
+ end
194
+ end
195
+
196
+ def parsed_condition_ary ary
197
+ ary = ary.compact
198
+ return [] unless ary && ary.first
199
+ case ary.first
200
+ when ActiveRecord::Base
201
+ ary.map { |obj| [obj.class.base_class.name, obj.id] }
202
+ when Hash
203
+ ary.map { |h| [h[:pasted_type].constantize.base_class.name, h[:pasted_id]] }
204
+ else
205
+ ary.map { |a| [a.first.constantize.base_class.name, a.last] }
206
+ end
207
+ end
208
+
209
+ def acts_as_pasted associations, prefix: '', **options
210
+ pasting_name = prefix.blank? ? "#{associations}_pastings".to_sym : [associations, prefix, 'pastings'].join('_').to_sym
211
+ has_many(pasting_name, -> { where(type: prefix) }, class_name: 'ActsAsPasting::Pasting', as: :pasted)
212
+ has_many(associations, -> { distinct }, through: pasting_name, source: :pasteable, **options)
213
+ end
214
+ end
215
+
216
+ module Association
217
+ def acts_as_pasted associations, options={}
218
+ has_many :pastings, as: :pasteable, class_name: 'ActsAsPasting::Pasting'
219
+ has_many associations, through: :pastings, source: :pasted, **options
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,28 @@
1
+ # == Schema Information
2
+ #
3
+ # Table name: pastings
4
+ #
5
+ # id :bigint not null, primary key
6
+ # pasteable_type :string(255)
7
+ # pasteable_id :bigint
8
+ # pasted_type :string(255)
9
+ # pasted_id :bigint
10
+ # type :string(255)
11
+ # created_at :datetime not null
12
+ # updated_at :datetime not null
13
+ # pasting_type :string(255)
14
+ #
15
+ # Indexes
16
+ #
17
+ # index_pastings_on_pasteable_type_and_pasteable_id (pasteable_type,pasteable_id)
18
+ # index_pastings_on_pasted_type_and_pasted_id (pasted_type,pasted_id)
19
+ # index_pastings_on_type (type)
20
+ #
21
+ module ActsAsPasting
22
+ class Pasting < ApplicationRecord
23
+ self.inheritance_column = :_type_disabled
24
+
25
+ belongs_to :pasteable, polymorphic: true
26
+ belongs_to :pasted, polymorphic: true
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsPasting
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,35 @@
1
+ require 'rails/generators/migration'
2
+
3
+ module ActsAsPasting
4
+ module Generators
5
+ class MigrationsGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+ desc 'Copy whaleback migrations to your application.'
8
+
9
+ def self.next_migration_number(dir)
10
+ sleep 1
11
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
12
+ end
13
+
14
+ source_root File.expand_path("../../../../db/migrate", __FILE__)
15
+
16
+ def copy_migrations
17
+ # Use sort() to order the migrations by seq
18
+ # Use [2..-1] to delete the seq
19
+ Dir[ File.join(self.class.source_root, '*.rb') ].sort.each { |f|
20
+ copy_migration File.basename(f, '.rb')
21
+ }
22
+ end
23
+
24
+ protected
25
+
26
+ def copy_migration(filename)
27
+ if self.class.migration_exists?("db/migrate", "#{filename[2..-1]}")
28
+ say_status("skipped", "Migration #{filename[2..-1]} already exists")
29
+ else
30
+ migration_template "#{filename}.rb", "db/migrate/#{filename[2..-1]}.rb"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
data/pasting.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'acts_as_pasting/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'acts_as_pasting'
8
+ spec.version = ActsAsPasting::VERSION
9
+ spec.authors = ['ivan Lan']
10
+ spec.email = ['mumumumushu@gmail.com']
11
+
12
+ spec.summary = %q{}
13
+ spec.description = %q{}
14
+ spec.homepage = 'https://git.tallty.com/open-source/acts_as_pasting'
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+
22
+ spec.bindir = 'exe'
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_development_dependency('bundler')
27
+ spec.add_development_dependency('rake')
28
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_pasting
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - ivan Lan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-05-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: ''
42
+ email:
43
+ - mumumumushu@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - README.md
51
+ - Rakefile
52
+ - bin/console
53
+ - bin/setup
54
+ - db/migrate/1_create_acts_as_pasting_pastings.rb
55
+ - lib/acts_as_pasting.rb
56
+ - lib/acts_as_pasting/errors.rb
57
+ - lib/acts_as_pasting/pastable.rb
58
+ - lib/acts_as_pasting/pasted.rb
59
+ - lib/acts_as_pasting/pasting.rb
60
+ - lib/acts_as_pasting/version.rb
61
+ - lib/generators/acts_as_pasting/migrations_generator.rb
62
+ - pasting.gemspec
63
+ homepage: https://git.tallty.com/open-source/acts_as_pasting
64
+ licenses: []
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.2.15
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: ''
85
+ test_files: []