simple_master 1.0.0
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 +7 -0
- data/lib/simple_master/active_record/belongs_to_master_polymorphic_reflection.rb +11 -0
- data/lib/simple_master/active_record/belongs_to_polymorphic_association.rb +17 -0
- data/lib/simple_master/active_record/belongs_to_polymorphic_builder.rb +21 -0
- data/lib/simple_master/active_record/extension.rb +183 -0
- data/lib/simple_master/active_record/preloader_association_extension.rb +11 -0
- data/lib/simple_master/active_record.rb +12 -0
- data/lib/simple_master/loader/dataset_loader.rb +20 -0
- data/lib/simple_master/loader/marshal_loader.rb +15 -0
- data/lib/simple_master/loader/query_loader.rb +63 -0
- data/lib/simple_master/loader.rb +55 -0
- data/lib/simple_master/master/association/belongs_to_association.rb +79 -0
- data/lib/simple_master/master/association/belongs_to_polymorphic_association.rb +79 -0
- data/lib/simple_master/master/association/has_many_association.rb +53 -0
- data/lib/simple_master/master/association/has_many_through_association.rb +64 -0
- data/lib/simple_master/master/association/has_one_association.rb +57 -0
- data/lib/simple_master/master/association.rb +50 -0
- data/lib/simple_master/master/column/bitmask_column.rb +74 -0
- data/lib/simple_master/master/column/boolean_column.rb +51 -0
- data/lib/simple_master/master/column/enum_column.rb +96 -0
- data/lib/simple_master/master/column/float_column.rb +21 -0
- data/lib/simple_master/master/column/id_column.rb +31 -0
- data/lib/simple_master/master/column/integer_column.rb +21 -0
- data/lib/simple_master/master/column/json_column.rb +27 -0
- data/lib/simple_master/master/column/polymorphic_type_column.rb +44 -0
- data/lib/simple_master/master/column/sti_type_column.rb +21 -0
- data/lib/simple_master/master/column/string_column.rb +17 -0
- data/lib/simple_master/master/column/symbol_column.rb +23 -0
- data/lib/simple_master/master/column/time_column.rb +38 -0
- data/lib/simple_master/master/column.rb +138 -0
- data/lib/simple_master/master/dsl.rb +239 -0
- data/lib/simple_master/master/editable.rb +155 -0
- data/lib/simple_master/master/filterable.rb +47 -0
- data/lib/simple_master/master/queryable.rb +75 -0
- data/lib/simple_master/master/storable.rb +20 -0
- data/lib/simple_master/master/validatable.rb +216 -0
- data/lib/simple_master/master.rb +417 -0
- data/lib/simple_master/schema.rb +49 -0
- data/lib/simple_master/storage/dataset.rb +172 -0
- data/lib/simple_master/storage/ondemand_table.rb +68 -0
- data/lib/simple_master/storage/table.rb +197 -0
- data/lib/simple_master/storage/test_table.rb +69 -0
- data/lib/simple_master/storage.rb +11 -0
- data/lib/simple_master/version.rb +5 -0
- data/lib/simple_master.rb +62 -0
- metadata +128 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3767dce67967051739f25da6dc14604e647a65bca3d6a68579b54cc0bad58009
|
|
4
|
+
data.tar.gz: ad1ffecd0e636e7072d8f1d928717ca42090a1e01426a1e619fca6a056a1720f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 28d28d28165fe704e8fa38b99a2a409b8c505c03b31366f2bb5ad7ad9bc033ca10c8e095ce725fe1b67e35cfc28da06f88d10c2dcb81545462f6cce911e2e21c
|
|
7
|
+
data.tar.gz: f92e380bbf3ff68fbd9686427d42c32f961342b73e4722f1a2634f6c2f2b72bcd97c919c33c76fdfa1f6afe208a8270b748b246eddedb6f6ee51d8e9baadd178
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
class BelongsToPolymorphicAssociation < ::ActiveRecord::Associations::BelongsToPolymorphicAssociation
|
|
6
|
+
def find_target(async: false)
|
|
7
|
+
klass = klass()
|
|
8
|
+
if klass < Master
|
|
9
|
+
foreign_key = owner.send(reflection.foreign_key)
|
|
10
|
+
klass.find(foreign_key)
|
|
11
|
+
else
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
class BelongsToPolymorphicBuilder < ::ActiveRecord::Associations::Builder::BelongsTo
|
|
6
|
+
def self.create_reflection(model, name, scope, options, &)
|
|
7
|
+
fail ArgumentError, "association names must be a Symbol" unless name.is_a?(Symbol)
|
|
8
|
+
|
|
9
|
+
validate_options(options)
|
|
10
|
+
|
|
11
|
+
extension = define_extensions(model, name, &)
|
|
12
|
+
options[:extend] = [*options[:extend], extension] if extension
|
|
13
|
+
|
|
14
|
+
scope = build_scope(scope)
|
|
15
|
+
|
|
16
|
+
fail "not implemented" if options[:through]
|
|
17
|
+
BelongsToMasterPolymorphicReflection.new(name, scope, options, model)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Extension
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
def master_dirty
|
|
10
|
+
@master_dirty ||= {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize_dup(*_)
|
|
14
|
+
@master_dirty = {}
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def simple_master_connection
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def simple_master_association_options
|
|
24
|
+
@simple_master_association_options ||= {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def belongs_to(name, scope = nil, **options)
|
|
28
|
+
unless options[:polymorphic]
|
|
29
|
+
class_name = options[:class_name] || ActiveSupport::Inflector.classify(name)
|
|
30
|
+
|
|
31
|
+
klass = compute_type(class_name.to_s)
|
|
32
|
+
if klass < SimpleMaster::Master
|
|
33
|
+
return belongs_to_master(name, options)
|
|
34
|
+
else
|
|
35
|
+
return super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
reflection = BelongsToPolymorphicBuilder.build(self, name, scope, options)
|
|
40
|
+
::ActiveRecord::Reflection.add_reflection self, name, reflection
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def belongs_to_master(name, options = EMPTY_HASH)
|
|
44
|
+
simple_master_association_options[name] = options.dup
|
|
45
|
+
|
|
46
|
+
class_name = options.delete(:class_name) || ActiveSupport::Inflector.classify(name)
|
|
47
|
+
foreign_key = options.delete(:foreign_key) || ActiveSupport::Inflector.foreign_key(name)
|
|
48
|
+
primary_key = options.delete(:primary_key) || :id
|
|
49
|
+
|
|
50
|
+
warn "Options not supported! #{options}" unless options.empty?
|
|
51
|
+
|
|
52
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
53
|
+
Module.new {
|
|
54
|
+
def #{name}
|
|
55
|
+
master_dirty[:#{name}] || #{class_name}.find_by_#{primary_key}(#{foreign_key})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def #{name}=(value)
|
|
59
|
+
if value == nil
|
|
60
|
+
self.#{foreign_key} = nil
|
|
61
|
+
master_dirty.delete(:#{name})
|
|
62
|
+
else
|
|
63
|
+
@_association_#{name}_source = self.#{foreign_key} = value.#{primary_key}
|
|
64
|
+
|
|
65
|
+
master_dirty[:#{name}] = value
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def #{foreign_key}=(value)
|
|
70
|
+
master_dirty.delete(:#{name})
|
|
71
|
+
super
|
|
72
|
+
end
|
|
73
|
+
}.tap { |mod| include mod }
|
|
74
|
+
|
|
75
|
+
before_save do
|
|
76
|
+
if master_dirty[:#{name}]
|
|
77
|
+
master = master_dirty.delete(:#{name})
|
|
78
|
+
# Skip saving the association if the key was changed after assignment
|
|
79
|
+
if @_association_#{name}_source != #{foreign_key}
|
|
80
|
+
next
|
|
81
|
+
end
|
|
82
|
+
master.save
|
|
83
|
+
self.#{name} = master
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
RUBY
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def has_one(name, scope = nil, **options)
|
|
90
|
+
return super if options[:through]
|
|
91
|
+
|
|
92
|
+
class_name = options[:class_name] || ActiveSupport::Inflector.classify(name)
|
|
93
|
+
|
|
94
|
+
klass = compute_type(class_name.to_s)
|
|
95
|
+
if klass < SimpleMaster::Master
|
|
96
|
+
has_one_master(name, options)
|
|
97
|
+
else
|
|
98
|
+
super
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def has_one_master(name, options = EMPTY_HASH)
|
|
103
|
+
simple_master_association_options[name] = options.dup
|
|
104
|
+
|
|
105
|
+
class_name = options.delete(:class_name) || ActiveSupport::Inflector.classify(name)
|
|
106
|
+
foreign_key = options.delete(:foreign_key) || ActiveSupport::Inflector.foreign_key(to_s)
|
|
107
|
+
primary_key = options.delete(:primary_key) || :id
|
|
108
|
+
|
|
109
|
+
warn "Options not supported! #{options}" unless options.empty?
|
|
110
|
+
|
|
111
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
112
|
+
Module.new {
|
|
113
|
+
def #{name}
|
|
114
|
+
#{class_name}.find_by(:#{foreign_key}, #{primary_key})
|
|
115
|
+
end
|
|
116
|
+
}.tap { |mod| include mod }
|
|
117
|
+
RUBY
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def has_many(name, scope = nil, **options)
|
|
121
|
+
return super if options[:through]
|
|
122
|
+
|
|
123
|
+
class_name = options[:class_name] || ActiveSupport::Inflector.classify(name)
|
|
124
|
+
|
|
125
|
+
klass = compute_type(class_name.to_s)
|
|
126
|
+
if klass < SimpleMaster::Master
|
|
127
|
+
has_many_master(name, options)
|
|
128
|
+
else
|
|
129
|
+
super
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def has_many_master(name, options = EMPTY_HASH)
|
|
134
|
+
simple_master_association_options[name] = options.dup
|
|
135
|
+
|
|
136
|
+
class_name = options.delete(:class_name) || ActiveSupport::Inflector.classify(name)
|
|
137
|
+
foreign_key = options.delete(:foreign_key) || ActiveSupport::Inflector.foreign_key(to_s)
|
|
138
|
+
primary_key = options.delete(:primary_key) || :id
|
|
139
|
+
inverse_of = options.delete(:inverse_of)
|
|
140
|
+
|
|
141
|
+
warn "Options not supported! #{options}" unless options.empty?
|
|
142
|
+
|
|
143
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
144
|
+
Module.new {
|
|
145
|
+
def #{name}
|
|
146
|
+
master_dirty[:#{name}] || #{class_name}.all_by(:#{foreign_key}, #{primary_key})
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def #{name}=(values)
|
|
150
|
+
master_dirty[:#{name}] = values
|
|
151
|
+
end
|
|
152
|
+
}.tap { |mod| include mod }
|
|
153
|
+
RUBY
|
|
154
|
+
|
|
155
|
+
if inverse_of
|
|
156
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
157
|
+
after_save do
|
|
158
|
+
if master_dirty[:#{name}]
|
|
159
|
+
master_dirty.delete(:#{name}).each do |master|
|
|
160
|
+
master.#{inverse_of} = self
|
|
161
|
+
master.save
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
RUBY
|
|
166
|
+
else
|
|
167
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
168
|
+
after_save do
|
|
169
|
+
if master_dirty[:#{name}]
|
|
170
|
+
master_dirty.delete(:#{name}).each do |master|
|
|
171
|
+
master.#{foreign_key} = self.id
|
|
172
|
+
master.save
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
RUBY
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
require "simple_master/active_record/belongs_to_master_polymorphic_reflection"
|
|
9
|
+
require "simple_master/active_record/belongs_to_polymorphic_association"
|
|
10
|
+
require "simple_master/active_record/belongs_to_polymorphic_builder"
|
|
11
|
+
require "simple_master/active_record/extension"
|
|
12
|
+
require "simple_master/active_record/preloader_association_extension"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Loader
|
|
5
|
+
class DatasetLoader < Loader
|
|
6
|
+
# options: dataset: ...
|
|
7
|
+
def read_raw(table)
|
|
8
|
+
dataset.table(table.klass)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def build_records(_klass, raw)
|
|
12
|
+
raw.all
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def dataset
|
|
16
|
+
@options[:dataset]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Loader
|
|
5
|
+
class MarshalLoader < Loader
|
|
6
|
+
def read_raw(table)
|
|
7
|
+
File.read("#{@options[:path]}/#{table.klass.table_name}.marshal")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def build_records(_klass, raw)
|
|
11
|
+
Marshal.load(raw) # rubocop:disable Security/MarshalLoad
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Loader
|
|
5
|
+
class QueryLoader < Loader
|
|
6
|
+
def read_raw(table)
|
|
7
|
+
klass = table.klass
|
|
8
|
+
unless klass.table_available?
|
|
9
|
+
return { columns: EMPTY_ARRAY, rows: EMPTY_ARRAY }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
result = klass.query_select_all
|
|
13
|
+
|
|
14
|
+
{ columns: result.columns, rows: result.rows }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build_records(klass, raw)
|
|
18
|
+
columns = raw[:columns].map(&:to_sym)
|
|
19
|
+
|
|
20
|
+
column_assign_methods = columns.map { :"#{_1}=" }
|
|
21
|
+
columns_hash = klass.columns_hash
|
|
22
|
+
sti_column_index = columns.find_index { columns_hash[_1].is_a?(Master::Column::StiTypeColumn) }
|
|
23
|
+
|
|
24
|
+
columns.each do |column_name|
|
|
25
|
+
unless klass.method_defined?(:"#{column_name}=")
|
|
26
|
+
if ENV["RAILS_ENV"] == "development"
|
|
27
|
+
# In local/dev, define a no-op setter so loading does not raise
|
|
28
|
+
klass.define_method(:"#{column_name}=", &:itself)
|
|
29
|
+
warn "#{klass}.#{column_name} column is not defined!"
|
|
30
|
+
else
|
|
31
|
+
fail "#{klass}.#{column_name} column is not defined!"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
raw[:rows].filter_map { |row|
|
|
37
|
+
begin
|
|
38
|
+
record =
|
|
39
|
+
if sti_column_index
|
|
40
|
+
child_klass = ActiveSupport::Inflector.constantize(row[sti_column_index])
|
|
41
|
+
if child_klass == klass || child_klass.sti_base_class == klass
|
|
42
|
+
child_klass.new
|
|
43
|
+
else
|
|
44
|
+
warn "[#{klass}] Invalid value in the type column: #{row}"
|
|
45
|
+
next
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
klass.new
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
column_assign_methods.zip(row) do |assign_method, value|
|
|
52
|
+
record.send(assign_method, value)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
record
|
|
56
|
+
rescue
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
}.freeze
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Loader
|
|
5
|
+
attr_reader :options
|
|
6
|
+
|
|
7
|
+
def initialize(options = {})
|
|
8
|
+
@options = options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Returns if records are updated in table.
|
|
12
|
+
def load_records(table)
|
|
13
|
+
klass = table.klass
|
|
14
|
+
fail "Load target not available." unless klass.base_class?
|
|
15
|
+
|
|
16
|
+
raw = read_raw(table)
|
|
17
|
+
|
|
18
|
+
new_digest = table.diff.nil? ? raw.hash : "#{raw.hash}/#{table.diff.hash}"
|
|
19
|
+
if new_digest == table.digest
|
|
20
|
+
return false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
table.digest = new_digest
|
|
24
|
+
table.all = build_records(klass, raw).freeze
|
|
25
|
+
globalize(table)
|
|
26
|
+
|
|
27
|
+
table.apply_diff
|
|
28
|
+
|
|
29
|
+
table.update_sub_tables
|
|
30
|
+
|
|
31
|
+
klass.reset_object_cache
|
|
32
|
+
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Interface for loader loading raw data, for building records and table digest.
|
|
37
|
+
def read_raw(table)
|
|
38
|
+
fail NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Interface for building records from raw data.
|
|
42
|
+
def build_records(klass, raw)
|
|
43
|
+
fail NotImplementedError
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def globalize(table)
|
|
47
|
+
return unless options[:globalize_proc]
|
|
48
|
+
table.all = options[:globalize_proc].call(table.klass, table.all)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
require "simple_master/loader/dataset_loader"
|
|
54
|
+
require "simple_master/loader/marshal_loader"
|
|
55
|
+
require "simple_master/loader/query_loader"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Master
|
|
5
|
+
class Association
|
|
6
|
+
class BelongsToAssociation < self
|
|
7
|
+
def foreign_key
|
|
8
|
+
@foreign_key ||= (options[:foreign_key] || ActiveSupport::Inflector.foreign_key(name)).to_sym
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def primary_key
|
|
12
|
+
@primary_key ||= options[:primary_key] || target_class.primary_key || :id
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def init(master_class)
|
|
16
|
+
master_class.simple_master_module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
17
|
+
if #{target_class} < SimpleMaster::Master
|
|
18
|
+
def #{name}
|
|
19
|
+
return nil unless #{foreign_key}
|
|
20
|
+
|
|
21
|
+
#{search_code}
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
def #{name}
|
|
25
|
+
return nil unless #{foreign_key}
|
|
26
|
+
|
|
27
|
+
belongs_to_store[:#{name}] ||= #{target_class}.simple_master_connection.find_by_#{primary_key}(#{foreign_key})
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
RUBY
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def init_for_test(master_class)
|
|
34
|
+
master_class.simple_master_module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
35
|
+
if #{target_class} < SimpleMaster::Master
|
|
36
|
+
def #{name}
|
|
37
|
+
return belongs_to_store[:#{name}] if belongs_to_store[:#{name}]
|
|
38
|
+
return nil unless #{foreign_key}
|
|
39
|
+
|
|
40
|
+
#{search_code}
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
def #{name}
|
|
44
|
+
return nil unless #{foreign_key}
|
|
45
|
+
|
|
46
|
+
belongs_to_store[:#{name}] ||= #{target_class}.simple_master_connection.find_by_#{primary_key}(#{foreign_key})
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def #{name}=(value)
|
|
51
|
+
@_association_#{name}_source = self.#{foreign_key} = value&.#{primary_key}
|
|
52
|
+
belongs_to_store[:#{name}] = value
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def _#{name}_target_save?
|
|
56
|
+
# Skip saving the association if the key changed after assignment
|
|
57
|
+
return false if @_association_#{name}_source != #{foreign_key}
|
|
58
|
+
|
|
59
|
+
target = belongs_to_store[:#{name}]
|
|
60
|
+
return false if target.nil?
|
|
61
|
+
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
RUBY
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def search_code
|
|
70
|
+
if primary_key == :id
|
|
71
|
+
"#{target_class}.find_by_id(#{foreign_key})"
|
|
72
|
+
else
|
|
73
|
+
"#{target_class}.find_by(:#{primary_key}, #{foreign_key})"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Master
|
|
5
|
+
class Association
|
|
6
|
+
class BelongsToPolymorphicAssociation < self
|
|
7
|
+
def foreign_key
|
|
8
|
+
@foreign_key ||= (options[:foreign_key] || ActiveSupport::Inflector.foreign_key(name)).to_sym
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def foreign_type
|
|
12
|
+
@foreign_type ||= options[:foreign_type] || :"#{name}_type"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def foreign_type_class
|
|
16
|
+
@foreign_type_class ||= :"#{foreign_type}_class"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def is_active_record?
|
|
20
|
+
# target_class will be dynamically loaded.
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def target_class
|
|
25
|
+
fail
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def init(master_class)
|
|
29
|
+
unless master_class.method_defined?(foreign_type_class)
|
|
30
|
+
fail "[#{master_class}] Please specify `polymorphic_type: true` on polymorphic type column <#{name}>."
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
master_class.simple_master_module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
34
|
+
def #{name}
|
|
35
|
+
return nil if #{foreign_type_class}.nil?
|
|
36
|
+
|
|
37
|
+
if #{foreign_type_class} < SimpleMaster::Master
|
|
38
|
+
#{foreign_type_class}.find_by_id(#{foreign_key})
|
|
39
|
+
else
|
|
40
|
+
belongs_to_store[:#{name}] ||= #{foreign_type_class}.simple_master_connection.find_by(id: #{foreign_key})
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
RUBY
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def init_for_test(master_class)
|
|
47
|
+
master_class.simple_master_module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
48
|
+
def #{name}
|
|
49
|
+
return nil if #{foreign_type_class}.nil?
|
|
50
|
+
|
|
51
|
+
if #{foreign_type_class} < SimpleMaster::Master
|
|
52
|
+
belongs_to_store[:#{name}] || #{foreign_type_class}.find_by_id(#{foreign_key})
|
|
53
|
+
else
|
|
54
|
+
belongs_to_store[:#{name}] ||= #{foreign_type_class}.simple_master_connection.find_by(id: #{foreign_key})
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def #{name}=(value)
|
|
59
|
+
@_association_#{name}_source = self.#{foreign_key} = value&.id
|
|
60
|
+
@_association_#{name}_class_source = self.#{foreign_type_class} = value&.class
|
|
61
|
+
belongs_to_store[:#{name}] = value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def _#{name}_target_save?
|
|
65
|
+
# Skip saving the association if the key changed after assignment
|
|
66
|
+
return false if @_association_#{name}_source != #{foreign_key}
|
|
67
|
+
return false if @_association_#{name}_class_source != #{foreign_type_class}
|
|
68
|
+
|
|
69
|
+
target = belongs_to_store[:#{name}]
|
|
70
|
+
return false if target.nil?
|
|
71
|
+
|
|
72
|
+
true
|
|
73
|
+
end
|
|
74
|
+
RUBY
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Master
|
|
5
|
+
class Association
|
|
6
|
+
class HasManyAssociation < self
|
|
7
|
+
def foreign_key
|
|
8
|
+
@foreign_key ||= (options[:foreign_key] || ActiveSupport::Inflector.foreign_key(defined_at.to_s)).to_sym
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def inverse_of
|
|
12
|
+
@inverse_of ||= options[:inverse_of] || ActiveSupport::Inflector.underscore(defined_at.to_s)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def primary_key
|
|
16
|
+
@primary_key ||= options[:primary_key] || defined_at.primary_key || :id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def init(master_class)
|
|
20
|
+
master_class.simple_master_module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
21
|
+
if #{target_class} < SimpleMaster::Master
|
|
22
|
+
def #{name}
|
|
23
|
+
#{target_class}.all_by(:#{foreign_key}, #{primary_key})
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
def #{name}
|
|
27
|
+
has_many_store[:#{name}] ||= #{target_class}.simple_master_connection.where(:#{foreign_key} => #{primary_key}).to_a
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
RUBY
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def init_for_test(master_class)
|
|
34
|
+
master_class.simple_master_module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
35
|
+
if #{target_class} < SimpleMaster::Master
|
|
36
|
+
def #{name}
|
|
37
|
+
has_many_store[:#{name}] || #{target_class}.all_by(:#{foreign_key}, #{primary_key})
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
def #{name}
|
|
41
|
+
has_many_store[:#{name}] ||= #{target_class}.simple_master_connection.where(:#{foreign_key} => #{primary_key}).to_a
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def #{name}=(values)
|
|
46
|
+
has_many_store[:#{name}] = values
|
|
47
|
+
end
|
|
48
|
+
RUBY
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|