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
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Master
|
|
5
|
+
class Errors
|
|
6
|
+
def initialize
|
|
7
|
+
@errors = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add(attribute, type = :invalid, **options)
|
|
11
|
+
@errors << [attribute, type, options]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
delegate :empty?, :map, :each, :size, to: :@errors
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Validator works similar to ActiveRecord::Validations::UniquenessValidator.
|
|
18
|
+
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
|
|
19
|
+
def initialize(options)
|
|
20
|
+
if options[:conditions] && !options[:conditions].respond_to?(:call)
|
|
21
|
+
fail ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \
|
|
22
|
+
"Pass a callable instead: `conditions: -> { where(approved: true) }`"
|
|
23
|
+
end
|
|
24
|
+
unless Array(options[:scope]).all? { |scope| scope.respond_to?(:to_sym) }
|
|
25
|
+
fail ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \
|
|
26
|
+
"Pass a symbol or an array of symbols instead: `scope: :user_id`"
|
|
27
|
+
end
|
|
28
|
+
super
|
|
29
|
+
@klass = options[:class]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate_each(record, attribute, value)
|
|
33
|
+
unless @klass.select { _1.send(attribute) == value }.one?
|
|
34
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
|
35
|
+
error_options[:value] = value
|
|
36
|
+
|
|
37
|
+
record.errors.add(attribute, :taken, **error_options)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module Validatable
|
|
43
|
+
extend ActiveSupport::Concern
|
|
44
|
+
|
|
45
|
+
#: () -> bool
|
|
46
|
+
def valid?
|
|
47
|
+
_run_validate_callbacks
|
|
48
|
+
# Refer directly instead of errors.empty? to avoid creating many errors objects
|
|
49
|
+
!Thread.current[:errors]&.[](self).present?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @rbs skip
|
|
53
|
+
def validate
|
|
54
|
+
_run_validate_callbacks
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def _run_validate_callbacks
|
|
58
|
+
self.class._validate_callbacks.each do |validation_proc|
|
|
59
|
+
instance_exec(&validation_proc)
|
|
60
|
+
rescue => e
|
|
61
|
+
errors.add(:base, "Error occurred while validation: #{e}")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def errors
|
|
66
|
+
Thread.current[:errors] ||= {}
|
|
67
|
+
Thread.current[:errors][self] ||= Errors.new
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def run_proc_or_call(symbol_or_proc)
|
|
71
|
+
return send(symbol_or_proc) if symbol_or_proc.is_a? Symbol
|
|
72
|
+
return instance_eval(&symbol_or_proc) if symbol_or_proc.arity == 1
|
|
73
|
+
instance_exec(&symbol_or_proc)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def read_attribute_for_validation(attribute)
|
|
77
|
+
send(attribute)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class_methods do
|
|
81
|
+
# Validation procs to be called by current class on each record
|
|
82
|
+
def _validate_procs
|
|
83
|
+
@_validate_procs ||= []
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Validation procs to be called by current and parent classes on each record
|
|
87
|
+
# (Similar to ActiveModel interface, but returns procs.)
|
|
88
|
+
def _validate_callbacks
|
|
89
|
+
if superclass < SimpleMaster::Master
|
|
90
|
+
superclass._validate_callbacks + _validate_procs
|
|
91
|
+
else
|
|
92
|
+
_validate_procs
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# ref: https://github.com/ruby/gem_rbs_collection/blob/54fe53bbe56e5ff4e9bc2cb2c95426f13abe77ca/gems/activemodel/6.0/activemodel.rbs#L48-L50
|
|
97
|
+
# @rbs!
|
|
98
|
+
# type condition[T] = Symbol | ^(T) [self: T] -> boolish
|
|
99
|
+
# type conditions[T] = condition[T] | Array[condition[T]]
|
|
100
|
+
|
|
101
|
+
# (ActiveModel interface compliant.)
|
|
102
|
+
# @rbs *validations: untyped
|
|
103
|
+
# @rbs **options: { on?: Symbol | Array[Symbol], if?: conditions[instance], unless?: conditions[instance] }
|
|
104
|
+
# @rbs &: ?{ (instance) [self: instance] -> void }
|
|
105
|
+
# @rbs return: void
|
|
106
|
+
def validate(*validations, **options, &)
|
|
107
|
+
conditions = options.slice(:if, :unless)
|
|
108
|
+
|
|
109
|
+
condition_procs = validation_condition_procs(conditions)
|
|
110
|
+
|
|
111
|
+
validations.each do |validation|
|
|
112
|
+
_validate_procs << proc {
|
|
113
|
+
run_proc_or_call(validation) if condition_procs.all? { run_proc_or_call(_1) }
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
_validate_procs << -> { instance_exec(&) if condition_procs.all? { run_proc_or_call(_1) } } if block_given?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# (ActiveModel interface compliant.)
|
|
120
|
+
# @rbs skip
|
|
121
|
+
def validates(*attributes)
|
|
122
|
+
defaults = attributes.extract_options!.dup
|
|
123
|
+
validations = defaults.slice!(:if, :unless, :on, :allow_blank, :allow_nil, :strict)
|
|
124
|
+
|
|
125
|
+
fail ArgumentError, "You need to supply at least one attribute" if attributes.empty?
|
|
126
|
+
fail ArgumentError, "You need to supply at least one validation" if validations.empty?
|
|
127
|
+
warn "Option :on is not supported" if defaults[:on]
|
|
128
|
+
warn "Option :strict is not supported" if defaults[:strict]
|
|
129
|
+
|
|
130
|
+
defaults[:attributes] = attributes
|
|
131
|
+
|
|
132
|
+
validations.each do |key, options|
|
|
133
|
+
key = "#{key.to_s.camelize}Validator"
|
|
134
|
+
|
|
135
|
+
validator = find_validator(key) || find_validator("ActiveModel::Validations::#{key}")
|
|
136
|
+
|
|
137
|
+
fail ArgumentError, "Unknown validator: '#{key}'" unless validator
|
|
138
|
+
|
|
139
|
+
next unless options
|
|
140
|
+
|
|
141
|
+
validates_with(validator, defaults.merge(_parse_validates_options(options)))
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# (ActiveModel interface compliant.)
|
|
146
|
+
# block args: (record, attribute, value)
|
|
147
|
+
def validates_each(*attributes, **options)
|
|
148
|
+
conditions = options.slice(:if, :unless)
|
|
149
|
+
condition_procs = validation_condition_procs(conditions)
|
|
150
|
+
attributes.each do |attribute|
|
|
151
|
+
_validate_procs << proc {
|
|
152
|
+
value = send(attribute)
|
|
153
|
+
next if options[:allow_nil] && value.nil?
|
|
154
|
+
next if options[:allow_blank] && value.blank?
|
|
155
|
+
yield(self, attribute, value) if condition_procs.all? { run_proc_or_call(_1) }
|
|
156
|
+
}
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# (ActiveModel interface compliant.)
|
|
161
|
+
def validates_with(*args, &)
|
|
162
|
+
options = args.extract_options!
|
|
163
|
+
options[:class] = self
|
|
164
|
+
|
|
165
|
+
conditions = options.slice(:if, :unless)
|
|
166
|
+
|
|
167
|
+
condition_procs = validation_condition_procs(conditions)
|
|
168
|
+
|
|
169
|
+
args.each do |klass|
|
|
170
|
+
validator = klass.new(options.dup, &)
|
|
171
|
+
|
|
172
|
+
_validate_procs << proc {
|
|
173
|
+
validator.validate(self) if condition_procs.all? { run_proc_or_call(_1) }
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def _parse_validates_options(options)
|
|
181
|
+
case options
|
|
182
|
+
when TrueClass
|
|
183
|
+
EMPTY_HASH
|
|
184
|
+
when Hash
|
|
185
|
+
options
|
|
186
|
+
when Range, Array
|
|
187
|
+
{ in: options }
|
|
188
|
+
else
|
|
189
|
+
{ with: options }
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def find_validator(key)
|
|
194
|
+
const_get(key)
|
|
195
|
+
rescue NameError
|
|
196
|
+
false
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# processing :if, :unless conditions
|
|
200
|
+
def validation_condition_procs(conditions)
|
|
201
|
+
conditions.flat_map do |key, value|
|
|
202
|
+
if key == :if
|
|
203
|
+
Array.wrap(value)
|
|
204
|
+
elsif key == :unless
|
|
205
|
+
Array.wrap(value).map { |v|
|
|
206
|
+
-> { !run_proc_or_call(v) }
|
|
207
|
+
}
|
|
208
|
+
else
|
|
209
|
+
EMPTY_ARRAY
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Master
|
|
5
|
+
autoload :Column, "simple_master/master/column"
|
|
6
|
+
autoload :Association, "simple_master/master/association"
|
|
7
|
+
autoload :Editable, "simple_master/master/editable"
|
|
8
|
+
autoload :Dsl, "simple_master/master/dsl"
|
|
9
|
+
autoload :Filterable, "simple_master/master/filterable"
|
|
10
|
+
autoload :Storable, "simple_master/master/storable"
|
|
11
|
+
autoload :Queryable, "simple_master/master/queryable"
|
|
12
|
+
autoload :Validatable, "simple_master/master/validatable"
|
|
13
|
+
|
|
14
|
+
extend Dsl
|
|
15
|
+
extend Filterable
|
|
16
|
+
extend Storable
|
|
17
|
+
extend Queryable
|
|
18
|
+
include Validatable
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
attr_accessor :abstract_class
|
|
22
|
+
|
|
23
|
+
#: () -> bool
|
|
24
|
+
def abstract_class?
|
|
25
|
+
!!abstract_class
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#: () -> bool
|
|
29
|
+
def table_exists?
|
|
30
|
+
table_available?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Auto-generated methods live in a module so they can be overridden.
|
|
34
|
+
def simple_master_module
|
|
35
|
+
@simple_master_module ||= Module.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# def simple_master_class_methods
|
|
39
|
+
# @simple_master_class_methods ||= Module.new
|
|
40
|
+
# end
|
|
41
|
+
|
|
42
|
+
#: () -> Array[Integer]
|
|
43
|
+
def ids
|
|
44
|
+
id_hash.keys
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#: () -> Array[SimpleMaster::Master::Column]
|
|
48
|
+
def columns
|
|
49
|
+
@columns ||= []
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#: () -> Array[SimpleMaster::Master::Column]
|
|
53
|
+
def all_columns
|
|
54
|
+
if superclass < SimpleMaster::Master
|
|
55
|
+
(superclass.all_columns + columns).reverse.uniq(&:name).reverse
|
|
56
|
+
else
|
|
57
|
+
columns
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def update_column_info(name)
|
|
62
|
+
if (column_index = columns.find_index { _1.name == name })
|
|
63
|
+
columns[column_index] = yield(columns[column_index])
|
|
64
|
+
return
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if (column_from_parent = superclass.all_columns.find { _1.name == name })
|
|
68
|
+
new_column = yield(column_from_parent)
|
|
69
|
+
columns << new_column
|
|
70
|
+
return
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
fail "Column #{name} not found on #{self}!"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#: () -> Array[String]
|
|
77
|
+
def column_names
|
|
78
|
+
all_columns.map(&:name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Unlike ActiveRecord, we ensure column existence can be checked.
|
|
82
|
+
#: () -> Hash[String, SimpleMaster::Master::Column]
|
|
83
|
+
def columns_hash
|
|
84
|
+
all_columns.index_by(&:name).with_indifferent_access
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def has_one_associations
|
|
88
|
+
@has_one_associations ||= []
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def all_has_one_associations
|
|
92
|
+
if superclass < SimpleMaster::Master
|
|
93
|
+
superclass.all_has_one_associations + has_one_associations
|
|
94
|
+
else
|
|
95
|
+
has_one_associations
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def has_many_associations
|
|
100
|
+
@has_many_associations ||= []
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def all_has_many_associations
|
|
104
|
+
if superclass < SimpleMaster::Master
|
|
105
|
+
superclass.all_has_many_associations + has_many_associations
|
|
106
|
+
else
|
|
107
|
+
has_many_associations
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def belongs_to_associations
|
|
112
|
+
@belongs_to_associations ||= []
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def all_belongs_to_associations
|
|
116
|
+
if superclass < SimpleMaster::Master
|
|
117
|
+
superclass.all_belongs_to_associations + belongs_to_associations
|
|
118
|
+
else
|
|
119
|
+
belongs_to_associations
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def group_keys
|
|
124
|
+
@group_keys || all_columns.select(&:group_key).map(&:name)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def sti_base_class
|
|
128
|
+
nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def sti_column
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @rbs skip
|
|
136
|
+
alias inheritance_column sti_column
|
|
137
|
+
|
|
138
|
+
def primary_key
|
|
139
|
+
:id
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @rbs skip
|
|
143
|
+
alias polymorphic_name name
|
|
144
|
+
|
|
145
|
+
def has_query_constraints?
|
|
146
|
+
false
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def composite_primary_key?
|
|
150
|
+
false
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def table_name
|
|
154
|
+
ActiveSupport::Inflector.tableize(base_class.to_s).tr("/", "_")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def current_scope
|
|
158
|
+
fail
|
|
159
|
+
# pp caller
|
|
160
|
+
# raise
|
|
161
|
+
# @current_scope ||= Scope.new
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
#: () -> void
|
|
165
|
+
def init(database_available = true, for_test: false)
|
|
166
|
+
@object_cache = Hash.new { |k, v| k[v] = {} }
|
|
167
|
+
|
|
168
|
+
_build_dsl
|
|
169
|
+
_build_columns(for_test)
|
|
170
|
+
_build_associations(database_available, for_test)
|
|
171
|
+
_build_sti_methods
|
|
172
|
+
|
|
173
|
+
# Override on test env.
|
|
174
|
+
define_method(:cache_object) { |_column, input, &block| block.call(input) } if for_test
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
#: () -> void
|
|
178
|
+
def dsl_initializers
|
|
179
|
+
@dsl_initializers ||= []
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
#: () -> void
|
|
183
|
+
def _build_dsl
|
|
184
|
+
dsl_initializers.each(&:call)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
#: () -> void
|
|
188
|
+
def _build_columns(for_test)
|
|
189
|
+
columns.each do |column|
|
|
190
|
+
column.init(self, for_test)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
@group_keys = group_keys
|
|
194
|
+
|
|
195
|
+
simple_master_module.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
196
|
+
def attributes
|
|
197
|
+
{
|
|
198
|
+
#{
|
|
199
|
+
all_columns.map { |column| "#{column.name}: #{column.name}" }.join(",\n")
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def db_attributes
|
|
205
|
+
{
|
|
206
|
+
#{
|
|
207
|
+
all_columns.map { |column| "#{column.db_column_name}: #{column.name}_value_for_csv" }.join(",\n")
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def globalized_db_attributes
|
|
213
|
+
{
|
|
214
|
+
#{
|
|
215
|
+
all_columns.map { |column| "#{column.db_column_name}: #{column.name}_value_for_csv" }.join(",\n")
|
|
216
|
+
},
|
|
217
|
+
#{
|
|
218
|
+
all_columns.select { |column| column.options[:globalize] }.map { |column|
|
|
219
|
+
"_globalized_#{column.name}: _globalized_#{column.name}"
|
|
220
|
+
}.join(",\n")
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def create_copy
|
|
226
|
+
r = self.class.default_object.dup
|
|
227
|
+
#{
|
|
228
|
+
all_columns.map { |column| "r.#{column.name} = #{column.name}" }.join("\n")
|
|
229
|
+
}
|
|
230
|
+
r
|
|
231
|
+
end
|
|
232
|
+
RUBY
|
|
233
|
+
|
|
234
|
+
build_default_object
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
#: () -> void
|
|
238
|
+
def _build_associations(is_database_available, for_test)
|
|
239
|
+
associations = has_one_associations + has_many_associations + belongs_to_associations
|
|
240
|
+
|
|
241
|
+
associations.each do |association|
|
|
242
|
+
next if !is_database_available && association.is_active_record?
|
|
243
|
+
association.init(self)
|
|
244
|
+
association.init_for_test(self) if for_test
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def _build_sti_methods
|
|
249
|
+
extend SubClassStorable if sti_sub_class?
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def inherited(subclass)
|
|
253
|
+
super
|
|
254
|
+
subclass.include(subclass.simple_master_module)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
attr_reader :class_method_cache_info #: Array[[Symbol, Proc]]
|
|
258
|
+
attr_reader :instance_methods_need_tap #: Array[Symbol]
|
|
259
|
+
attr_reader :default_object #: self
|
|
260
|
+
attr_reader :object_cache #: Hash[Symbol, untyped]
|
|
261
|
+
|
|
262
|
+
# @rbs return: Array[[Symbol, Proc]]
|
|
263
|
+
def all_class_method_cache_info
|
|
264
|
+
if superclass < SimpleMaster::Master
|
|
265
|
+
superclass.all_class_method_cache_info + (class_method_cache_info || EMPTY_ARRAY)
|
|
266
|
+
else
|
|
267
|
+
class_method_cache_info || EMPTY_ARRAY
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def scope(*_option)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def sti_class?
|
|
275
|
+
!!sti_base_class
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def sti_base_class?
|
|
279
|
+
sti_base_class == self
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def sti_sub_class?
|
|
283
|
+
!!sti_base_class && sti_base_class != self
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def base_class
|
|
287
|
+
sti_base_class || self
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def base_class?
|
|
291
|
+
!sti_base_class || sti_base_class?
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def globalized?
|
|
295
|
+
all_columns.any? { |column| column.options[:globalize] }
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
#: () -> void
|
|
299
|
+
def reset_object_cache
|
|
300
|
+
@object_cache.clear
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
#: () -> void
|
|
304
|
+
def build_default_object
|
|
305
|
+
default_object = allocate
|
|
306
|
+
|
|
307
|
+
all_columns.each do |column|
|
|
308
|
+
# Assign nil even when undefined to keep this fast
|
|
309
|
+
default_object.send :"#{column.name}=", column.options[:default].freeze
|
|
310
|
+
default_object.send :"_globalized_#{column.name}=", nil if column.options[:globalize]
|
|
311
|
+
end
|
|
312
|
+
default_object.type = name if sti_class?
|
|
313
|
+
|
|
314
|
+
@default_object = default_object
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
#: (Symbol, untyped) { (untyped) -> untyped } -> untyped
|
|
319
|
+
def cache_object(column, input)
|
|
320
|
+
cache = self.class.base_class.object_cache[column]
|
|
321
|
+
cache.fetch(input) { cache[input] = yield(input) }
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def instance_store
|
|
325
|
+
store = RequestStore.store
|
|
326
|
+
instance_store = store.fetch(:instance_store) { store[:instance_store] = Hash.new { |hash, key| hash[key] = {} }.compare_by_identity }
|
|
327
|
+
instance_store[self]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def has_many_store
|
|
331
|
+
RequestStore.store[:has_many_store] ||= {}.compare_by_identity
|
|
332
|
+
RequestStore.store[:has_many_store][self] ||= {}
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def belongs_to_store
|
|
336
|
+
RequestStore.store[:belongs_to_store] ||= {}.compare_by_identity
|
|
337
|
+
RequestStore.store[:belongs_to_store][self] ||= {}
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def dirty!
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
alias [] send
|
|
344
|
+
|
|
345
|
+
def _read_attribute(key)
|
|
346
|
+
instance_variable_get("@#{key}")
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
alias read_attribute _read_attribute
|
|
350
|
+
|
|
351
|
+
def slice(*attrs)
|
|
352
|
+
attrs.index_with { send(_1) }
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def as_json(_option = nil)
|
|
356
|
+
attributes
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# for comparing in test.
|
|
360
|
+
def json_slice(*attrs)
|
|
361
|
+
JSON.parse(slice(*attrs).to_json)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
#: () -> bool
|
|
365
|
+
def destroyed?
|
|
366
|
+
false
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
#: () -> bool
|
|
370
|
+
def new_record?
|
|
371
|
+
false
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
#: () -> bool
|
|
375
|
+
def has_changes_to_save?
|
|
376
|
+
false
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
#: () -> bool
|
|
380
|
+
def marked_for_destruction?
|
|
381
|
+
false
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def save(*_)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def save!(*_)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def update(*_)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def update!(*_)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def destroy
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def destroy!
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def inspect
|
|
403
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)} #{attributes.inspect}>"
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def initialize(attributes = nil)
|
|
407
|
+
attributes&.each do |key, value|
|
|
408
|
+
send :"#{key}=", value
|
|
409
|
+
end
|
|
410
|
+
yield self if block_given?
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def self.new(attributes = nil, &)
|
|
414
|
+
default_object.dup.tap { _1.send(:initialize, attributes, &) }
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMaster
|
|
4
|
+
class Schema
|
|
5
|
+
class << self
|
|
6
|
+
TYPE = {
|
|
7
|
+
integer: :integer,
|
|
8
|
+
float: :float,
|
|
9
|
+
string: :string,
|
|
10
|
+
text: :string,
|
|
11
|
+
json: :json,
|
|
12
|
+
boolean: :boolean,
|
|
13
|
+
datetime: :time,
|
|
14
|
+
time: :time,
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def generate(table_name)
|
|
18
|
+
ar_columns = ::ActiveRecord::Base.connection.columns(table_name)
|
|
19
|
+
ar_columns.each do |ar_column|
|
|
20
|
+
info = []
|
|
21
|
+
|
|
22
|
+
info << " def_column :#{ar_column.name}"
|
|
23
|
+
|
|
24
|
+
if ar_column.name == "type"
|
|
25
|
+
info << "sti: true"
|
|
26
|
+
elsif ar_column.name.end_with?("type") && ar_columns.any? { _1.name == "#{ar_column.name.delete_suffix('_type')}_id" }
|
|
27
|
+
info << "polymorphic_type: true"
|
|
28
|
+
elsif ar_column.name != "id" && TYPE[ar_column.type]
|
|
29
|
+
info << "type: :#{TYPE[ar_column.type]}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
unless ar_column.default.nil?
|
|
33
|
+
db_default_value = db_default_value(ar_column)
|
|
34
|
+
|
|
35
|
+
info << "default: #{db_default_value.inspect}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if ar_column.type == :time
|
|
39
|
+
info << "db_type: :time"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
puts info.join(", ")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|