all_futures 1.0.3 → 2.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 +4 -4
- data/Gemfile +4 -1
- data/Gemfile.lock +272 -61
- data/LICENSE.txt +1 -1
- data/README.md +59 -79
- data/Rakefile +13 -1
- data/SUMMARY.md +22 -2
- data/bin/console +8 -0
- data/bin/loc +0 -0
- data/bin/setup +0 -0
- data/bin/standardize +0 -0
- data/lib/all_futures/association.rb +42 -0
- data/lib/all_futures/association_index.rb +69 -0
- data/lib/all_futures/attributes.rb +28 -0
- data/lib/all_futures/base.rb +66 -0
- data/lib/all_futures/callbacks.rb +71 -0
- data/lib/all_futures/configuration.rb +9 -0
- data/lib/all_futures/dirty.rb +116 -0
- data/lib/all_futures/embeds.rb +40 -0
- data/lib/all_futures/errors.rb +37 -0
- data/lib/all_futures/finder.rb +214 -0
- data/lib/all_futures/locale/en.yml +49 -0
- data/lib/all_futures/persistence.rb +595 -0
- data/lib/all_futures/presenter.rb +35 -0
- data/lib/all_futures/railtie.rb +26 -0
- data/lib/all_futures/translation.rb +22 -0
- data/lib/all_futures/validations.rb +10 -0
- data/lib/all_futures/version.rb +3 -1
- data/lib/all_futures/versions.rb +80 -0
- data/lib/all_futures.rb +24 -1
- data/lib/vendor/ACTIVE_ENTITY_MIT-LICENSE +42 -0
- data/lib/vendor/README.md +8 -0
- data/lib/vendor/active_entity/aggregations.rb +277 -0
- data/lib/vendor/active_entity/associations/embeds/association.rb +134 -0
- data/lib/vendor/active_entity/associations/embeds/builder/association.rb +105 -0
- data/lib/vendor/active_entity/associations/embeds/builder/collection_association.rb +71 -0
- data/lib/vendor/active_entity/associations/embeds/builder/embedded_in.rb +38 -0
- data/lib/vendor/active_entity/associations/embeds/builder/embeds_many.rb +13 -0
- data/lib/vendor/active_entity/associations/embeds/builder/embeds_one.rb +16 -0
- data/lib/vendor/active_entity/associations/embeds/builder/singular_association.rb +28 -0
- data/lib/vendor/active_entity/associations/embeds/collection_association.rb +209 -0
- data/lib/vendor/active_entity/associations/embeds/collection_proxy.rb +310 -0
- data/lib/vendor/active_entity/associations/embeds/embedded_in_association.rb +31 -0
- data/lib/vendor/active_entity/associations/embeds/embeds_many_association.rb +15 -0
- data/lib/vendor/active_entity/associations/embeds/embeds_one_association.rb +26 -0
- data/lib/vendor/active_entity/associations/embeds/singular_association.rb +35 -0
- data/lib/vendor/active_entity/associations.rb +168 -0
- data/lib/vendor/active_entity/attribute_assignment.rb +87 -0
- data/lib/vendor/active_entity/attribute_decorators.rb +90 -0
- data/lib/vendor/active_entity/attribute_methods/before_type_cast.rb +78 -0
- data/lib/vendor/active_entity/attribute_methods/dirty.rb +180 -0
- data/lib/vendor/active_entity/attribute_methods/primary_key.rb +96 -0
- data/lib/vendor/active_entity/attribute_methods/query.rb +38 -0
- data/lib/vendor/active_entity/attribute_methods/read.rb +44 -0
- data/lib/vendor/active_entity/attribute_methods/serialization.rb +90 -0
- data/lib/vendor/active_entity/attribute_methods/time_zone_conversion.rb +93 -0
- data/lib/vendor/active_entity/attribute_methods/write.rb +54 -0
- data/lib/vendor/active_entity/attribute_methods.rb +833 -0
- data/lib/vendor/active_entity/attributes.rb +240 -0
- data/lib/vendor/active_entity/base.rb +293 -0
- data/lib/vendor/active_entity/callbacks.rb +21 -0
- data/lib/vendor/active_entity/coders/json.rb +15 -0
- data/lib/vendor/active_entity/coders/yaml_column.rb +50 -0
- data/lib/vendor/active_entity/core.rb +348 -0
- data/lib/vendor/active_entity/enum.rb +260 -0
- data/lib/vendor/active_entity/errors.rb +69 -0
- data/lib/vendor/active_entity/gem_version.rb +17 -0
- data/lib/vendor/active_entity/inheritance.rb +176 -0
- data/lib/vendor/active_entity/integration.rb +78 -0
- data/lib/vendor/active_entity/locale/en.yml +45 -0
- data/lib/vendor/active_entity/model_schema.rb +118 -0
- data/lib/vendor/active_entity/nested_attributes.rb +554 -0
- data/lib/vendor/active_entity/persistence.rb +20 -0
- data/lib/vendor/active_entity/railtie.rb +76 -0
- data/lib/vendor/active_entity/readonly_attributes.rb +55 -0
- data/lib/vendor/active_entity/reflection.rb +447 -0
- data/lib/vendor/active_entity/serialization.rb +28 -0
- data/lib/vendor/active_entity/store.rb +291 -0
- data/lib/vendor/active_entity/translation.rb +24 -0
- data/lib/vendor/active_entity/type/date.rb +9 -0
- data/lib/vendor/active_entity/type/date_time.rb +9 -0
- data/lib/vendor/active_entity/type/internal/timezone.rb +17 -0
- data/lib/vendor/active_entity/type/json.rb +30 -0
- data/lib/vendor/active_entity/type/modifiers/array.rb +72 -0
- data/lib/vendor/active_entity/type/modifiers/array_without_blank.rb +19 -0
- data/lib/vendor/active_entity/type/registry.rb +112 -0
- data/lib/vendor/active_entity/type/serialized.rb +71 -0
- data/lib/vendor/active_entity/type/text.rb +11 -0
- data/lib/vendor/active_entity/type/time.rb +21 -0
- data/lib/vendor/active_entity/type/unsigned_integer.rb +17 -0
- data/lib/vendor/active_entity/type.rb +75 -0
- data/lib/vendor/active_entity/validate_embeds_association.rb +300 -0
- data/lib/vendor/active_entity/validations/absence.rb +25 -0
- data/lib/vendor/active_entity/validations/associated.rb +60 -0
- data/lib/vendor/active_entity/validations/length.rb +26 -0
- data/lib/vendor/active_entity/validations/presence.rb +68 -0
- data/lib/vendor/active_entity/validations/subset.rb +76 -0
- data/lib/vendor/active_entity/validations/uniqueness_in_embeds.rb +91 -0
- data/lib/vendor/active_entity/validations/uniqueness_on_active_record.rb +234 -0
- data/lib/vendor/active_entity/validations.rb +47 -0
- data/lib/vendor/active_entity/version.rb +10 -0
- data/lib/vendor/active_entity.rb +99 -0
- data/lib/vendor/core_ext/array_without_blank.rb +48 -0
- metadata +175 -21
- data/lib/all_futures/all_futures.rb +0 -28
- data/lib/all_futures/config.rb +0 -14
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/map"
|
|
4
|
+
require "active_support/core_ext/enumerable"
|
|
5
|
+
|
|
6
|
+
module ActiveEntity
|
|
7
|
+
module AMAttributeMethods
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
|
|
11
|
+
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
|
|
12
|
+
|
|
13
|
+
included do
|
|
14
|
+
class_attribute :attribute_aliases, instance_writer: false, default: {}
|
|
15
|
+
class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module ClassMethods
|
|
19
|
+
# Declares a method available for all attributes with the given prefix.
|
|
20
|
+
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
|
|
21
|
+
#
|
|
22
|
+
# #{prefix}#{attr}(*args, &block)
|
|
23
|
+
#
|
|
24
|
+
# to
|
|
25
|
+
#
|
|
26
|
+
# #{prefix}attribute(#{attr}, *args, &block)
|
|
27
|
+
#
|
|
28
|
+
# An instance method <tt>#{prefix}attribute</tt> must exist and accept
|
|
29
|
+
# at least the +attr+ argument.
|
|
30
|
+
#
|
|
31
|
+
# class Person
|
|
32
|
+
# include ActiveModel::AttributeMethods
|
|
33
|
+
#
|
|
34
|
+
# attr_accessor :name
|
|
35
|
+
# attribute_method_prefix 'clear_'
|
|
36
|
+
# define_attribute_methods :name
|
|
37
|
+
#
|
|
38
|
+
# private
|
|
39
|
+
#
|
|
40
|
+
# def clear_attribute(attr)
|
|
41
|
+
# send("#{attr}=", nil)
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# person = Person.new
|
|
46
|
+
# person.name = 'Bob'
|
|
47
|
+
# person.name # => "Bob"
|
|
48
|
+
# person.clear_name
|
|
49
|
+
# person.name # => nil
|
|
50
|
+
def attribute_method_prefix(*prefixes)
|
|
51
|
+
self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
|
|
52
|
+
undefine_attribute_methods
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Declares a method available for all attributes with the given suffix.
|
|
56
|
+
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
|
|
57
|
+
#
|
|
58
|
+
# #{attr}#{suffix}(*args, &block)
|
|
59
|
+
#
|
|
60
|
+
# to
|
|
61
|
+
#
|
|
62
|
+
# attribute#{suffix}(#{attr}, *args, &block)
|
|
63
|
+
#
|
|
64
|
+
# An <tt>attribute#{suffix}</tt> instance method must exist and accept at
|
|
65
|
+
# least the +attr+ argument.
|
|
66
|
+
#
|
|
67
|
+
# class Person
|
|
68
|
+
# include ActiveModel::AttributeMethods
|
|
69
|
+
#
|
|
70
|
+
# attr_accessor :name
|
|
71
|
+
# attribute_method_suffix '_short?'
|
|
72
|
+
# define_attribute_methods :name
|
|
73
|
+
#
|
|
74
|
+
# private
|
|
75
|
+
#
|
|
76
|
+
# def attribute_short?(attr)
|
|
77
|
+
# send(attr).length < 5
|
|
78
|
+
# end
|
|
79
|
+
# end
|
|
80
|
+
#
|
|
81
|
+
# person = Person.new
|
|
82
|
+
# person.name = 'Bob'
|
|
83
|
+
# person.name # => "Bob"
|
|
84
|
+
# person.name_short? # => true
|
|
85
|
+
def attribute_method_suffix(*suffixes)
|
|
86
|
+
self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
|
|
87
|
+
undefine_attribute_methods
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Declares a method available for all attributes with the given prefix
|
|
91
|
+
# and suffix. Uses +method_missing+ and <tt>respond_to?</tt> to rewrite
|
|
92
|
+
# the method.
|
|
93
|
+
#
|
|
94
|
+
# #{prefix}#{attr}#{suffix}(*args, &block)
|
|
95
|
+
#
|
|
96
|
+
# to
|
|
97
|
+
#
|
|
98
|
+
# #{prefix}attribute#{suffix}(#{attr}, *args, &block)
|
|
99
|
+
#
|
|
100
|
+
# An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
|
|
101
|
+
# accept at least the +attr+ argument.
|
|
102
|
+
#
|
|
103
|
+
# class Person
|
|
104
|
+
# include ActiveModel::AttributeMethods
|
|
105
|
+
#
|
|
106
|
+
# attr_accessor :name
|
|
107
|
+
# attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
|
|
108
|
+
# define_attribute_methods :name
|
|
109
|
+
#
|
|
110
|
+
# private
|
|
111
|
+
#
|
|
112
|
+
# def reset_attribute_to_default!(attr)
|
|
113
|
+
# send("#{attr}=", 'Default Name')
|
|
114
|
+
# end
|
|
115
|
+
# end
|
|
116
|
+
#
|
|
117
|
+
# person = Person.new
|
|
118
|
+
# person.name # => 'Gem'
|
|
119
|
+
# person.reset_name_to_default!
|
|
120
|
+
# person.name # => 'Default Name'
|
|
121
|
+
def attribute_method_affix(*affixes)
|
|
122
|
+
self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
|
|
123
|
+
undefine_attribute_methods
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Allows you to make aliases for attributes.
|
|
127
|
+
#
|
|
128
|
+
# class Person
|
|
129
|
+
# include ActiveModel::AttributeMethods
|
|
130
|
+
#
|
|
131
|
+
# attr_accessor :name
|
|
132
|
+
# attribute_method_suffix '_short?'
|
|
133
|
+
# define_attribute_methods :name
|
|
134
|
+
#
|
|
135
|
+
# alias_attribute :nickname, :name
|
|
136
|
+
#
|
|
137
|
+
# private
|
|
138
|
+
#
|
|
139
|
+
# def attribute_short?(attr)
|
|
140
|
+
# send(attr).length < 5
|
|
141
|
+
# end
|
|
142
|
+
# end
|
|
143
|
+
#
|
|
144
|
+
# person = Person.new
|
|
145
|
+
# person.name = 'Bob'
|
|
146
|
+
# person.name # => "Bob"
|
|
147
|
+
# person.nickname # => "Bob"
|
|
148
|
+
# person.name_short? # => true
|
|
149
|
+
# person.nickname_short? # => true
|
|
150
|
+
def alias_attribute(new_name, old_name)
|
|
151
|
+
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
|
|
152
|
+
CodeGenerator.batch(self, __FILE__, __LINE__) do |owner|
|
|
153
|
+
attribute_method_matchers.each do |matcher|
|
|
154
|
+
matcher_new = matcher.method_name(new_name).to_s
|
|
155
|
+
matcher_old = matcher.method_name(old_name).to_s
|
|
156
|
+
define_proxy_call false, owner, matcher_new, matcher_old
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Is +new_name+ an alias?
|
|
162
|
+
def attribute_alias?(new_name)
|
|
163
|
+
attribute_aliases.key? new_name.to_s
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns the original name for the alias +name+
|
|
167
|
+
def attribute_alias(name)
|
|
168
|
+
attribute_aliases[name.to_s]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Declares the attributes that should be prefixed and suffixed by
|
|
172
|
+
# <tt>ActiveModel::AttributeMethods</tt>.
|
|
173
|
+
#
|
|
174
|
+
# To use, pass attribute names (as strings or symbols). Be sure to declare
|
|
175
|
+
# +define_attribute_methods+ after you define any prefix, suffix or affix
|
|
176
|
+
# methods, or they will not hook in.
|
|
177
|
+
#
|
|
178
|
+
# class Person
|
|
179
|
+
# include ActiveModel::AttributeMethods
|
|
180
|
+
#
|
|
181
|
+
# attr_accessor :name, :age, :address
|
|
182
|
+
# attribute_method_prefix 'clear_'
|
|
183
|
+
#
|
|
184
|
+
# # Call to define_attribute_methods must appear after the
|
|
185
|
+
# # attribute_method_prefix, attribute_method_suffix or
|
|
186
|
+
# # attribute_method_affix declarations.
|
|
187
|
+
# define_attribute_methods :name, :age, :address
|
|
188
|
+
#
|
|
189
|
+
# private
|
|
190
|
+
#
|
|
191
|
+
# def clear_attribute(attr)
|
|
192
|
+
# send("#{attr}=", nil)
|
|
193
|
+
# end
|
|
194
|
+
# end
|
|
195
|
+
def define_attribute_methods(*attr_names)
|
|
196
|
+
CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
|
|
197
|
+
attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Declares an attribute that should be prefixed and suffixed by
|
|
202
|
+
# <tt>ActiveModel::AttributeMethods</tt>.
|
|
203
|
+
#
|
|
204
|
+
# To use, pass an attribute name (as string or symbol). Be sure to declare
|
|
205
|
+
# +define_attribute_method+ after you define any prefix, suffix or affix
|
|
206
|
+
# method, or they will not hook in.
|
|
207
|
+
#
|
|
208
|
+
# class Person
|
|
209
|
+
# include ActiveModel::AttributeMethods
|
|
210
|
+
#
|
|
211
|
+
# attr_accessor :name
|
|
212
|
+
# attribute_method_suffix '_short?'
|
|
213
|
+
#
|
|
214
|
+
# # Call to define_attribute_method must appear after the
|
|
215
|
+
# # attribute_method_prefix, attribute_method_suffix or
|
|
216
|
+
# # attribute_method_affix declarations.
|
|
217
|
+
# define_attribute_method :name
|
|
218
|
+
#
|
|
219
|
+
# private
|
|
220
|
+
#
|
|
221
|
+
# def attribute_short?(attr)
|
|
222
|
+
# send(attr).length < 5
|
|
223
|
+
# end
|
|
224
|
+
# end
|
|
225
|
+
#
|
|
226
|
+
# person = Person.new
|
|
227
|
+
# person.name = 'Bob'
|
|
228
|
+
# person.name # => "Bob"
|
|
229
|
+
# person.name_short? # => true
|
|
230
|
+
def define_attribute_method(attr_name, _owner: generated_attribute_methods)
|
|
231
|
+
CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
|
|
232
|
+
attribute_method_matchers.each do |matcher|
|
|
233
|
+
method_name = matcher.method_name(attr_name)
|
|
234
|
+
|
|
235
|
+
unless instance_method_already_implemented?(method_name)
|
|
236
|
+
generate_method = "define_method_#{matcher.target}"
|
|
237
|
+
|
|
238
|
+
if respond_to?(generate_method, true)
|
|
239
|
+
send(generate_method, attr_name.to_s, owner: owner)
|
|
240
|
+
else
|
|
241
|
+
define_proxy_call true, owner, method_name, matcher.target, attr_name.to_s
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
attribute_method_matchers_cache.clear
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Removes all the previously dynamically defined methods from the class.
|
|
250
|
+
#
|
|
251
|
+
# class Person
|
|
252
|
+
# include ActiveModel::AttributeMethods
|
|
253
|
+
#
|
|
254
|
+
# attr_accessor :name
|
|
255
|
+
# attribute_method_suffix '_short?'
|
|
256
|
+
# define_attribute_method :name
|
|
257
|
+
#
|
|
258
|
+
# private
|
|
259
|
+
#
|
|
260
|
+
# def attribute_short?(attr)
|
|
261
|
+
# send(attr).length < 5
|
|
262
|
+
# end
|
|
263
|
+
# end
|
|
264
|
+
#
|
|
265
|
+
# person = Person.new
|
|
266
|
+
# person.name = 'Bob'
|
|
267
|
+
# person.name_short? # => true
|
|
268
|
+
#
|
|
269
|
+
# Person.undefine_attribute_methods
|
|
270
|
+
#
|
|
271
|
+
# person.name_short? # => NoMethodError
|
|
272
|
+
def undefine_attribute_methods
|
|
273
|
+
generated_attribute_methods.module_eval do
|
|
274
|
+
undef_method(*instance_methods)
|
|
275
|
+
end
|
|
276
|
+
attribute_method_matchers_cache.clear
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
private
|
|
280
|
+
class CodeGenerator
|
|
281
|
+
class << self
|
|
282
|
+
def batch(owner, path, line)
|
|
283
|
+
if owner.is_a?(CodeGenerator)
|
|
284
|
+
yield owner
|
|
285
|
+
else
|
|
286
|
+
instance = new(owner, path, line)
|
|
287
|
+
result = yield instance
|
|
288
|
+
instance.execute
|
|
289
|
+
result
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def initialize(owner, path, line)
|
|
295
|
+
@owner = owner
|
|
296
|
+
@path = path
|
|
297
|
+
@line = line
|
|
298
|
+
@sources = ["# frozen_string_literal: true\n"]
|
|
299
|
+
@renames = {}
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def <<(source_line)
|
|
303
|
+
@sources << source_line
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def rename_method(old_name, new_name)
|
|
307
|
+
@renames[old_name] = new_name
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def execute
|
|
311
|
+
@owner.module_eval(@sources.join(";"), @path, @line - 1)
|
|
312
|
+
@renames.each do |old_name, new_name|
|
|
313
|
+
@owner.alias_method new_name, old_name
|
|
314
|
+
@owner.undef_method old_name
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
private_constant :CodeGenerator
|
|
319
|
+
|
|
320
|
+
def generated_attribute_methods
|
|
321
|
+
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def instance_method_already_implemented?(method_name)
|
|
325
|
+
generated_attribute_methods.method_defined?(method_name)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# The methods +method_missing+ and +respond_to?+ of this module are
|
|
329
|
+
# invoked often in a typical rails, both of which invoke the method
|
|
330
|
+
# +matched_attribute_method+. The latter method iterates through an
|
|
331
|
+
# array doing regular expression matches, which results in a lot of
|
|
332
|
+
# object creations. Most of the time it returns a +nil+ match. As the
|
|
333
|
+
# match result is always the same given a +method_name+, this cache is
|
|
334
|
+
# used to alleviate the GC, which ultimately also speeds up the app
|
|
335
|
+
# significantly (in our case our test suite finishes 10% faster with
|
|
336
|
+
# this cache).
|
|
337
|
+
def attribute_method_matchers_cache
|
|
338
|
+
@attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def attribute_method_matchers_matching(method_name)
|
|
342
|
+
attribute_method_matchers_cache.compute_if_absent(method_name) do
|
|
343
|
+
attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Define a method `name` in `mod` that dispatches to `send`
|
|
348
|
+
# using the given `extra` args. This falls back on `define_method`
|
|
349
|
+
# and `send` if the given names cannot be compiled.
|
|
350
|
+
def define_proxy_call(include_private, code_generator, name, target, *extra)
|
|
351
|
+
defn = if NAME_COMPILABLE_REGEXP.match?(name)
|
|
352
|
+
"def #{name}(*args)"
|
|
353
|
+
else
|
|
354
|
+
"define_method(:'#{name}') do |*args|"
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
extra = (extra.map!(&:inspect) << "*args").join(", ")
|
|
358
|
+
|
|
359
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target)
|
|
360
|
+
"#{"self." unless include_private}#{target}(#{extra})"
|
|
361
|
+
else
|
|
362
|
+
"send(:'#{target}', #{extra})"
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
code_generator <<
|
|
366
|
+
defn <<
|
|
367
|
+
body <<
|
|
368
|
+
"end" <<
|
|
369
|
+
"ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)"
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
class AttributeMethodMatcher #:nodoc:
|
|
373
|
+
attr_reader :prefix, :suffix, :target
|
|
374
|
+
|
|
375
|
+
AttributeMethodMatch = Struct.new(:target, :attr_name)
|
|
376
|
+
|
|
377
|
+
def initialize(options = {})
|
|
378
|
+
@prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
|
|
379
|
+
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
|
|
380
|
+
@target = "#{@prefix}attribute#{@suffix}"
|
|
381
|
+
@method_name = "#{prefix}%s#{suffix}"
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def match(method_name)
|
|
385
|
+
if @regex =~ method_name
|
|
386
|
+
AttributeMethodMatch.new(target, $1)
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def method_name(attr_name)
|
|
391
|
+
@method_name % attr_name
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Allows access to the object attributes, which are held in the hash
|
|
397
|
+
# returned by <tt>attributes</tt>, as though they were first-class
|
|
398
|
+
# methods. So a +Person+ class with a +name+ attribute can for example use
|
|
399
|
+
# <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use
|
|
400
|
+
# the attributes hash -- except for multiple assignments with
|
|
401
|
+
# <tt>ActiveRecord::Base#attributes=</tt>.
|
|
402
|
+
#
|
|
403
|
+
# It's also possible to instantiate related objects, so a <tt>Client</tt>
|
|
404
|
+
# class belonging to the +clients+ table with a +master_id+ foreign key
|
|
405
|
+
# can instantiate master through <tt>Client#master</tt>.
|
|
406
|
+
def method_missing(method, *args, &block)
|
|
407
|
+
if respond_to_without_attributes?(method, true)
|
|
408
|
+
super
|
|
409
|
+
else
|
|
410
|
+
match = matched_attribute_method(method.to_s)
|
|
411
|
+
match ? attribute_missing(match, *args, &block) : super
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
|
415
|
+
|
|
416
|
+
# +attribute_missing+ is like +method_missing+, but for attributes. When
|
|
417
|
+
# +method_missing+ is called we check to see if there is a matching
|
|
418
|
+
# attribute method. If so, we tell +attribute_missing+ to dispatch the
|
|
419
|
+
# attribute. This method can be overloaded to customize the behavior.
|
|
420
|
+
def attribute_missing(match, *args, &block)
|
|
421
|
+
__send__(match.target, match.attr_name, *args, &block)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# A +Person+ instance with a +name+ attribute can ask
|
|
425
|
+
# <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
|
|
426
|
+
# and <tt>person.respond_to?(:name?)</tt> which will all return +true+.
|
|
427
|
+
alias :respond_to_without_attributes? :respond_to?
|
|
428
|
+
def respond_to?(method, include_private_methods = false)
|
|
429
|
+
if super
|
|
430
|
+
true
|
|
431
|
+
elsif !include_private_methods && super(method, true)
|
|
432
|
+
# If we're here then we haven't found among non-private methods
|
|
433
|
+
# but found among all methods. Which means that the given method is private.
|
|
434
|
+
false
|
|
435
|
+
else
|
|
436
|
+
!matched_attribute_method(method.to_s).nil?
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
private
|
|
441
|
+
def attribute_method?(attr_name)
|
|
442
|
+
respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# Returns a struct representing the matching attribute method.
|
|
446
|
+
# The struct's attributes are prefix, base and suffix.
|
|
447
|
+
def matched_attribute_method(method_name)
|
|
448
|
+
matches = self.class.send(:attribute_method_matchers_matching, method_name)
|
|
449
|
+
matches.detect { |match| attribute_method?(match.attr_name) }
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def missing_attribute(attr_name, stack)
|
|
453
|
+
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def _read_attribute(attr)
|
|
457
|
+
__send__(attr)
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
module AttrNames # :nodoc:
|
|
461
|
+
DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
|
|
462
|
+
|
|
463
|
+
# We want to generate the methods via module_eval rather than
|
|
464
|
+
# define_method, because define_method is slower on dispatch.
|
|
465
|
+
# Evaluating many similar methods may use more memory as the instruction
|
|
466
|
+
# sequences are duplicated and cached (in MRI). define_method may
|
|
467
|
+
# be slower on dispatch, but if you're careful about the closure
|
|
468
|
+
# created, then define_method will consume much less memory.
|
|
469
|
+
#
|
|
470
|
+
# But sometimes the database might return columns with
|
|
471
|
+
# characters that are not allowed in normal method names (like
|
|
472
|
+
# 'my_column(omg)'. So to work around this we first define with
|
|
473
|
+
# the __temp__ identifier, and then use alias method to rename
|
|
474
|
+
# it to what we want.
|
|
475
|
+
#
|
|
476
|
+
# We are also defining a constant to hold the frozen string of
|
|
477
|
+
# the attribute name. Using a constant means that we do not have
|
|
478
|
+
# to allocate an object on each call to the attribute method.
|
|
479
|
+
# Making it frozen means that it doesn't get duped when used to
|
|
480
|
+
# key the @attributes in read_attribute.
|
|
481
|
+
def self.define_attribute_accessor_method(owner, attr_name, writer: false)
|
|
482
|
+
method_name = "#{attr_name}#{'=' if writer}"
|
|
483
|
+
if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
|
|
484
|
+
yield method_name, "'#{attr_name}'"
|
|
485
|
+
else
|
|
486
|
+
safe_name = attr_name.unpack1("h*")
|
|
487
|
+
const_name = "ATTR_#{safe_name}"
|
|
488
|
+
const_set(const_name, attr_name) unless const_defined?(const_name)
|
|
489
|
+
temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
|
|
490
|
+
attr_name_expr = "::ActiveEntity::AMAttributeMethods::AttrNames::#{const_name}"
|
|
491
|
+
yield temp_method_name, attr_name_expr
|
|
492
|
+
owner.rename_method(temp_method_name, method_name)
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# = Active Entity Attribute Methods
|
|
499
|
+
module AttributeMethods
|
|
500
|
+
extend ActiveSupport::Concern
|
|
501
|
+
include AMAttributeMethods # Some AM modules will include AM::AttributeMethods, we want override it
|
|
502
|
+
|
|
503
|
+
included do
|
|
504
|
+
initialize_generated_modules
|
|
505
|
+
include Read
|
|
506
|
+
include Write
|
|
507
|
+
include BeforeTypeCast
|
|
508
|
+
include Query
|
|
509
|
+
include PrimaryKey
|
|
510
|
+
include TimeZoneConversion
|
|
511
|
+
include Dirty
|
|
512
|
+
include Serialization
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
|
|
516
|
+
|
|
517
|
+
class GeneratedAttributeMethods < Module #:nodoc:
|
|
518
|
+
# mutex_m was removed from Ruby 3.4's default gems; use an internal
|
|
519
|
+
# mutex instead, mirroring the fix in Rails 7.1 (rails/rails#49371)
|
|
520
|
+
def initialize
|
|
521
|
+
super
|
|
522
|
+
@mutex = Mutex.new
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def synchronize(&block)
|
|
526
|
+
@mutex.synchronize(&block)
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
class << self
|
|
531
|
+
def dangerous_attribute_methods # :nodoc:
|
|
532
|
+
@dangerous_attribute_methods ||= (
|
|
533
|
+
Base.instance_methods +
|
|
534
|
+
Base.private_instance_methods -
|
|
535
|
+
Base.superclass.instance_methods -
|
|
536
|
+
Base.superclass.private_instance_methods
|
|
537
|
+
).map { |m| -m.to_s }.to_set.freeze
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
module ClassMethods
|
|
542
|
+
def inherited(child_class) #:nodoc:
|
|
543
|
+
child_class.initialize_generated_modules
|
|
544
|
+
super
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def initialize_generated_modules # :nodoc:
|
|
548
|
+
@generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
|
|
549
|
+
private_constant :GeneratedAttributeMethods
|
|
550
|
+
@attribute_methods_generated = false
|
|
551
|
+
include @generated_attribute_methods
|
|
552
|
+
|
|
553
|
+
super
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Generates all the attribute related methods for columns in the database
|
|
557
|
+
# accessors, mutators and query methods.
|
|
558
|
+
def define_attribute_methods # :nodoc:
|
|
559
|
+
return false if @attribute_methods_generated
|
|
560
|
+
# Use a mutex; we don't want two threads simultaneously trying to define
|
|
561
|
+
# attribute methods.
|
|
562
|
+
generated_attribute_methods.synchronize do
|
|
563
|
+
return false if @attribute_methods_generated
|
|
564
|
+
superclass.define_attribute_methods unless base_class?
|
|
565
|
+
super(attribute_names)
|
|
566
|
+
@attribute_methods_generated = true
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def undefine_attribute_methods # :nodoc:
|
|
571
|
+
generated_attribute_methods.synchronize do
|
|
572
|
+
super if defined?(@attribute_methods_generated) && @attribute_methods_generated
|
|
573
|
+
@attribute_methods_generated = false
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# Raises an ActiveEntity::DangerousAttributeError exception when an
|
|
578
|
+
# \Active \Record method is defined in the model, otherwise +false+.
|
|
579
|
+
#
|
|
580
|
+
# class Person < ActiveEntity::Base
|
|
581
|
+
# def save
|
|
582
|
+
# 'already defined by Active Entity'
|
|
583
|
+
# end
|
|
584
|
+
# end
|
|
585
|
+
#
|
|
586
|
+
# Person.instance_method_already_implemented?(:save)
|
|
587
|
+
# # => ActiveEntity::DangerousAttributeError: save is defined by Active Entity. Check to make sure that you don't have an attribute or method with the same name.
|
|
588
|
+
#
|
|
589
|
+
# Person.instance_method_already_implemented?(:name)
|
|
590
|
+
# # => false
|
|
591
|
+
def instance_method_already_implemented?(method_name)
|
|
592
|
+
if dangerous_attribute_method?(method_name)
|
|
593
|
+
raise DangerousAttributeError, "#{method_name} is defined by Active Entity. Check to make sure that you don't have an attribute or method with the same name."
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
if superclass == Base
|
|
597
|
+
super
|
|
598
|
+
else
|
|
599
|
+
# If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
|
|
600
|
+
# defines its own attribute method, then we don't want to overwrite that.
|
|
601
|
+
defined = method_defined_within?(method_name, superclass, Base) &&
|
|
602
|
+
! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
|
|
603
|
+
defined || super
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# A method name is 'dangerous' if it is already (re)defined by Active Entity, but
|
|
608
|
+
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
|
|
609
|
+
def dangerous_attribute_method?(name) # :nodoc:
|
|
610
|
+
::ActiveEntity::AttributeMethods.dangerous_attribute_methods.include?(name.to_s)
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
|
|
614
|
+
if klass.method_defined?(name) || klass.private_method_defined?(name)
|
|
615
|
+
if superklass.method_defined?(name) || superklass.private_method_defined?(name)
|
|
616
|
+
klass.instance_method(name).owner != superklass.instance_method(name).owner
|
|
617
|
+
else
|
|
618
|
+
true
|
|
619
|
+
end
|
|
620
|
+
else
|
|
621
|
+
false
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
# A class method is 'dangerous' if it is already (re)defined by Active Entity, but
|
|
626
|
+
# not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
|
|
627
|
+
def dangerous_class_method?(method_name)
|
|
628
|
+
return true if RESTRICTED_CLASS_METHODS.include?(method_name.to_s)
|
|
629
|
+
|
|
630
|
+
if Base.respond_to?(method_name, true)
|
|
631
|
+
if Object.respond_to?(method_name, true)
|
|
632
|
+
Base.method(method_name).owner != Object.method(method_name).owner
|
|
633
|
+
else
|
|
634
|
+
true
|
|
635
|
+
end
|
|
636
|
+
else
|
|
637
|
+
false
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Returns an array of column names as strings if it's not an abstract class.
|
|
642
|
+
# Otherwise it returns an empty array.
|
|
643
|
+
#
|
|
644
|
+
# class Person < ActiveEntity::Base
|
|
645
|
+
# end
|
|
646
|
+
#
|
|
647
|
+
# Person.attribute_names
|
|
648
|
+
# # => ["id", "created_at", "updated_at", "name", "age"]
|
|
649
|
+
def attribute_names
|
|
650
|
+
@attribute_names ||= if !abstract_class?
|
|
651
|
+
attribute_types.keys
|
|
652
|
+
else
|
|
653
|
+
[]
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
# Returns true if the given attribute exists, otherwise false.
|
|
658
|
+
#
|
|
659
|
+
# class Person < ActiveEntity::Base
|
|
660
|
+
# end
|
|
661
|
+
#
|
|
662
|
+
# Person.has_attribute?('name') # => true
|
|
663
|
+
# Person.has_attribute?('new_name') # => true
|
|
664
|
+
# Person.has_attribute?(:age) # => true
|
|
665
|
+
# Person.has_attribute?(:nothing) # => false
|
|
666
|
+
def has_attribute?(attr_name)
|
|
667
|
+
attr_name = attr_name.to_s
|
|
668
|
+
attr_name = attribute_aliases[attr_name] || attr_name
|
|
669
|
+
attribute_types.key?(attr_name)
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def _has_attribute?(attr_name) # :nodoc:
|
|
673
|
+
attribute_types.key?(attr_name)
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
# https://github.com/rails/rails/blob/df475877efdcf74d7524f734ab8ad1d4704fd187/activemodel/lib/active_model/attribute_methods.rb#L208-L217
|
|
677
|
+
def alias_attribute(new_name, old_name)
|
|
678
|
+
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
|
|
679
|
+
CodeGenerator.batch(self, __FILE__, __LINE__) do |owner|
|
|
680
|
+
attribute_method_matchers.each do |matcher|
|
|
681
|
+
matcher_new = matcher.method_name(new_name).to_s
|
|
682
|
+
matcher_old = matcher.method_name(old_name).to_s
|
|
683
|
+
define_proxy_call false, owner, matcher_new, matcher_old
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
|
|
690
|
+
#
|
|
691
|
+
# class Person < ActiveEntity::Base
|
|
692
|
+
# alias_attribute :new_name, :name
|
|
693
|
+
# end
|
|
694
|
+
#
|
|
695
|
+
# person = Person.new
|
|
696
|
+
# person.has_attribute?(:name) # => true
|
|
697
|
+
# person.has_attribute?(:new_name) # => true
|
|
698
|
+
# person.has_attribute?('age') # => true
|
|
699
|
+
# person.has_attribute?(:nothing) # => false
|
|
700
|
+
def has_attribute?(attr_name)
|
|
701
|
+
attr_name = attr_name.to_s
|
|
702
|
+
attr_name = self.class.attribute_aliases[attr_name] || attr_name
|
|
703
|
+
@attributes.key?(attr_name)
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def _has_attribute?(attr_name) # :nodoc:
|
|
707
|
+
@attributes.key?(attr_name)
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# Returns an array of names for the attributes available on this object.
|
|
711
|
+
#
|
|
712
|
+
# class Person < ActiveEntity::Base
|
|
713
|
+
# end
|
|
714
|
+
#
|
|
715
|
+
# person = Person.new
|
|
716
|
+
# person.attribute_names
|
|
717
|
+
# # => ["id", "created_at", "updated_at", "name", "age"]
|
|
718
|
+
def attribute_names
|
|
719
|
+
@attributes.keys
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
|
723
|
+
#
|
|
724
|
+
# class Person < ActiveEntity::Base
|
|
725
|
+
# end
|
|
726
|
+
#
|
|
727
|
+
# person = Person.create(name: 'Francesco', age: 22)
|
|
728
|
+
# person.attributes
|
|
729
|
+
# # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
|
|
730
|
+
def attributes
|
|
731
|
+
@attributes.to_hash
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
# Returns an <tt>#inspect</tt>-like string for the value of the
|
|
735
|
+
# attribute +attr_name+. String attributes are truncated up to 50
|
|
736
|
+
# characters, Date and Time attributes are returned in the
|
|
737
|
+
# <tt>:db</tt> format. Other attributes return the value of
|
|
738
|
+
# <tt>#inspect</tt> without modification.
|
|
739
|
+
#
|
|
740
|
+
# person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
|
|
741
|
+
#
|
|
742
|
+
# person.attribute_for_inspect(:name)
|
|
743
|
+
# # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
|
|
744
|
+
#
|
|
745
|
+
# person.attribute_for_inspect(:created_at)
|
|
746
|
+
# # => "\"2012-10-22 00:15:07\""
|
|
747
|
+
#
|
|
748
|
+
# person.attribute_for_inspect(:tag_ids)
|
|
749
|
+
# # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
|
|
750
|
+
def attribute_for_inspect(attr_name)
|
|
751
|
+
attr_name = attr_name.to_s
|
|
752
|
+
value = _read_attribute(attr_name)
|
|
753
|
+
format_for_inspect(value)
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
# Returns +true+ if the specified +attribute+ has been set by the user or by a
|
|
757
|
+
# database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
|
|
758
|
+
# to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
|
|
759
|
+
# Note that it always returns +true+ with boolean attributes.
|
|
760
|
+
#
|
|
761
|
+
# class Task < ActiveEntity::Base
|
|
762
|
+
# end
|
|
763
|
+
#
|
|
764
|
+
# task = Task.new(title: '', is_done: false)
|
|
765
|
+
# task.attribute_present?(:title) # => false
|
|
766
|
+
# task.attribute_present?(:is_done) # => true
|
|
767
|
+
# task.title = 'Buy milk'
|
|
768
|
+
# task.is_done = true
|
|
769
|
+
# task.attribute_present?(:title) # => true
|
|
770
|
+
# task.attribute_present?(:is_done) # => true
|
|
771
|
+
def attribute_present?(attr_name)
|
|
772
|
+
attr_name = attr_name.to_s
|
|
773
|
+
value = _read_attribute(attr_name)
|
|
774
|
+
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
|
778
|
+
# "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
|
|
779
|
+
# <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
|
|
780
|
+
#
|
|
781
|
+
# Note: +:id+ is always present.
|
|
782
|
+
#
|
|
783
|
+
# class Person < ActiveEntity::Base
|
|
784
|
+
# belongs_to :organization
|
|
785
|
+
# end
|
|
786
|
+
#
|
|
787
|
+
# person = Person.new(name: 'Francesco', age: '22')
|
|
788
|
+
# person[:name] # => "Francesco"
|
|
789
|
+
# person[:age] # => 22
|
|
790
|
+
#
|
|
791
|
+
# person = Person.select('id').first
|
|
792
|
+
# person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
|
|
793
|
+
# person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
|
|
794
|
+
def [](attr_name)
|
|
795
|
+
read_attribute(attr_name) { |n| missing_attribute(n, caller) }
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
|
799
|
+
# (Alias for the protected #write_attribute method).
|
|
800
|
+
#
|
|
801
|
+
# class Person < ActiveEntity::Base
|
|
802
|
+
# end
|
|
803
|
+
#
|
|
804
|
+
# person = Person.new
|
|
805
|
+
# person[:age] = '22'
|
|
806
|
+
# person[:age] # => 22
|
|
807
|
+
# person[:age].class # => Integer
|
|
808
|
+
def []=(attr_name, value)
|
|
809
|
+
write_attribute(attr_name, value)
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
private
|
|
813
|
+
|
|
814
|
+
def attribute_method?(attr_name)
|
|
815
|
+
# We check defined? because Syck calls respond_to? before actually calling initialize.
|
|
816
|
+
defined?(@attributes) && @attributes.key?(attr_name)
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
def format_for_inspect(value)
|
|
820
|
+
if value.is_a?(String) && value.length > 50
|
|
821
|
+
"#{value[0, 50]}...".inspect
|
|
822
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
|
823
|
+
%("#{value.to_s(:db)}")
|
|
824
|
+
else
|
|
825
|
+
value.inspect
|
|
826
|
+
end
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
def pk_attribute?(name)
|
|
830
|
+
name == @primary_key
|
|
831
|
+
end
|
|
832
|
+
end
|
|
833
|
+
end
|