acts_as_pasting 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []