activeentity 0.0.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +42 -0
  3. data/README.md +145 -0
  4. data/Rakefile +29 -0
  5. data/lib/active_entity.rb +73 -0
  6. data/lib/active_entity/aggregations.rb +276 -0
  7. data/lib/active_entity/associations.rb +146 -0
  8. data/lib/active_entity/associations/embedded/association.rb +134 -0
  9. data/lib/active_entity/associations/embedded/builder/association.rb +100 -0
  10. data/lib/active_entity/associations/embedded/builder/collection_association.rb +69 -0
  11. data/lib/active_entity/associations/embedded/builder/embedded_in.rb +38 -0
  12. data/lib/active_entity/associations/embedded/builder/embeds_many.rb +13 -0
  13. data/lib/active_entity/associations/embedded/builder/embeds_one.rb +16 -0
  14. data/lib/active_entity/associations/embedded/builder/singular_association.rb +28 -0
  15. data/lib/active_entity/associations/embedded/collection_association.rb +188 -0
  16. data/lib/active_entity/associations/embedded/collection_proxy.rb +310 -0
  17. data/lib/active_entity/associations/embedded/embedded_in_association.rb +31 -0
  18. data/lib/active_entity/associations/embedded/embeds_many_association.rb +15 -0
  19. data/lib/active_entity/associations/embedded/embeds_one_association.rb +19 -0
  20. data/lib/active_entity/associations/embedded/singular_association.rb +35 -0
  21. data/lib/active_entity/attribute_assignment.rb +85 -0
  22. data/lib/active_entity/attribute_decorators.rb +90 -0
  23. data/lib/active_entity/attribute_methods.rb +330 -0
  24. data/lib/active_entity/attribute_methods/before_type_cast.rb +78 -0
  25. data/lib/active_entity/attribute_methods/primary_key.rb +98 -0
  26. data/lib/active_entity/attribute_methods/query.rb +35 -0
  27. data/lib/active_entity/attribute_methods/read.rb +47 -0
  28. data/lib/active_entity/attribute_methods/serialization.rb +90 -0
  29. data/lib/active_entity/attribute_methods/time_zone_conversion.rb +91 -0
  30. data/lib/active_entity/attribute_methods/write.rb +63 -0
  31. data/lib/active_entity/attributes.rb +165 -0
  32. data/lib/active_entity/base.rb +303 -0
  33. data/lib/active_entity/coders/json.rb +15 -0
  34. data/lib/active_entity/coders/yaml_column.rb +50 -0
  35. data/lib/active_entity/core.rb +281 -0
  36. data/lib/active_entity/define_callbacks.rb +17 -0
  37. data/lib/active_entity/enum.rb +234 -0
  38. data/lib/active_entity/errors.rb +80 -0
  39. data/lib/active_entity/gem_version.rb +17 -0
  40. data/lib/active_entity/inheritance.rb +278 -0
  41. data/lib/active_entity/integration.rb +78 -0
  42. data/lib/active_entity/locale/en.yml +45 -0
  43. data/lib/active_entity/model_schema.rb +115 -0
  44. data/lib/active_entity/nested_attributes.rb +592 -0
  45. data/lib/active_entity/readonly_attributes.rb +47 -0
  46. data/lib/active_entity/reflection.rb +441 -0
  47. data/lib/active_entity/serialization.rb +25 -0
  48. data/lib/active_entity/store.rb +242 -0
  49. data/lib/active_entity/translation.rb +24 -0
  50. data/lib/active_entity/type.rb +73 -0
  51. data/lib/active_entity/type/date.rb +9 -0
  52. data/lib/active_entity/type/date_time.rb +9 -0
  53. data/lib/active_entity/type/decimal_without_scale.rb +15 -0
  54. data/lib/active_entity/type/hash_lookup_type_map.rb +25 -0
  55. data/lib/active_entity/type/internal/timezone.rb +17 -0
  56. data/lib/active_entity/type/json.rb +30 -0
  57. data/lib/active_entity/type/modifiers/array.rb +72 -0
  58. data/lib/active_entity/type/registry.rb +92 -0
  59. data/lib/active_entity/type/serialized.rb +71 -0
  60. data/lib/active_entity/type/text.rb +11 -0
  61. data/lib/active_entity/type/time.rb +21 -0
  62. data/lib/active_entity/type/type_map.rb +62 -0
  63. data/lib/active_entity/type/unsigned_integer.rb +17 -0
  64. data/lib/active_entity/validate_embedded_association.rb +305 -0
  65. data/lib/active_entity/validations.rb +50 -0
  66. data/lib/active_entity/validations/absence.rb +25 -0
  67. data/lib/active_entity/validations/associated.rb +60 -0
  68. data/lib/active_entity/validations/length.rb +26 -0
  69. data/lib/active_entity/validations/presence.rb +68 -0
  70. data/lib/active_entity/validations/subset.rb +76 -0
  71. data/lib/active_entity/validations/uniqueness_in_embedding.rb +99 -0
  72. data/lib/active_entity/version.rb +10 -0
  73. data/lib/tasks/active_entity_tasks.rake +6 -0
  74. metadata +155 -0
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embedded
6
+ # = Active Entity Belongs To Association
7
+ class EmbeddedInAssociation < SingularAssociation #:nodoc:
8
+ def default(&block)
9
+ writer(owner.instance_exec(&block)) if reader.nil?
10
+ end
11
+
12
+ private
13
+
14
+ def replace(record)
15
+ if record
16
+ raise_on_type_mismatch!(record)
17
+ set_inverse_instance(record)
18
+ end
19
+
20
+ self.target = record
21
+ end
22
+ # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
23
+ # has_one associations.
24
+ def invertible_for?(record)
25
+ inverse = inverse_reflection_for(record)
26
+ inverse&.embeds_one?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embedded
6
+ # = Active Entity Has Many Association
7
+ # This is the proxy that handles a has many association.
8
+ #
9
+ # If the association has a <tt>:through</tt> option further specialization
10
+ # is provided by its child HasManyThroughAssociation.
11
+ class EmbedsManyAssociation < CollectionAssociation #:nodoc:
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embedded
6
+ # = Active Entity Has One Association
7
+ class EmbedsOneAssociation < SingularAssociation #:nodoc:
8
+ private
9
+ def replace(record)
10
+ raise_on_type_mismatch!(record) if record
11
+
12
+ return target unless record
13
+
14
+ self.target = record
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embedded
6
+ class SingularAssociation < Association #:nodoc:
7
+ # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
8
+ def reader
9
+ target
10
+ end
11
+
12
+ # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
13
+ def writer(record)
14
+ replace(record)
15
+ end
16
+
17
+ def build(attributes = {}, &block)
18
+ record = build_record(attributes, &block)
19
+ set_new_record(record)
20
+ record
21
+ end
22
+
23
+ private
24
+
25
+ def replace(_record)
26
+ raise NotImplementedError, "Subclasses must implement a replace(record) method"
27
+ end
28
+
29
+ def set_new_record(record)
30
+ replace(record)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/forbidden_attributes_protection"
4
+
5
+ module ActiveEntity
6
+ module AttributeAssignment
7
+ include ActiveModel::AttributeAssignment
8
+
9
+ private
10
+
11
+ def _assign_attributes(attributes)
12
+ multi_parameter_attributes = {}
13
+ nested_parameter_attributes = {}
14
+
15
+ attributes.each do |k, v|
16
+ if k.to_s.include?("(")
17
+ multi_parameter_attributes[k] = attributes.delete(k)
18
+ elsif v.is_a?(Hash)
19
+ nested_parameter_attributes[k] = attributes.delete(k)
20
+ end
21
+ end
22
+ super(attributes)
23
+
24
+ assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
25
+ assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
26
+ end
27
+
28
+ # Assign any deferred nested attributes after the base attributes have been set.
29
+ def assign_nested_parameter_attributes(pairs)
30
+ pairs.each { |k, v| _assign_attribute(k, v) }
31
+ end
32
+
33
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
34
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
35
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
36
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
37
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
38
+ # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
39
+ def assign_multiparameter_attributes(pairs)
40
+ execute_callstack_for_multiparameter_attributes(
41
+ extract_callstack_for_multiparameter_attributes(pairs)
42
+ )
43
+ end
44
+
45
+ def execute_callstack_for_multiparameter_attributes(callstack)
46
+ errors = []
47
+ callstack.each do |name, values_with_empty_parameters|
48
+ if values_with_empty_parameters.each_value.all?(&:nil?)
49
+ values = nil
50
+ else
51
+ values = values_with_empty_parameters
52
+ end
53
+ send("#{name}=", values)
54
+ rescue => ex
55
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
56
+ end
57
+ unless errors.empty?
58
+ error_descriptions = errors.map(&:message).join(",")
59
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
60
+ end
61
+ end
62
+
63
+ def extract_callstack_for_multiparameter_attributes(pairs)
64
+ attributes = {}
65
+
66
+ pairs.each do |(multiparameter_name, value)|
67
+ attribute_name = multiparameter_name.split("(").first
68
+ attributes[attribute_name] ||= {}
69
+
70
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
71
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
72
+ end
73
+
74
+ attributes
75
+ end
76
+
77
+ def type_cast_attribute_value(multiparameter_name, value)
78
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
79
+ end
80
+
81
+ def find_parameter_position(multiparameter_name)
82
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module AttributeDecorators # :nodoc:
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :attribute_type_decorations, instance_accessor: false, default: TypeDecorator.new # :internal:
9
+ end
10
+
11
+ module ClassMethods # :nodoc:
12
+ # This method is an internal API used to create class macros such as
13
+ # +serialize+, and features like time zone aware attributes.
14
+ #
15
+ # Used to wrap the type of an attribute in a new type.
16
+ # When the schema for a model is loaded, attributes with the same name as
17
+ # +column_name+ will have their type yielded to the given block. The
18
+ # return value of that block will be used instead.
19
+ #
20
+ # Subsequent calls where +column_name+ and +decorator_name+ are the same
21
+ # will override the previous decorator, not decorate twice. This can be
22
+ # used to create idempotent class macros like +serialize+
23
+ def decorate_attribute_type(column_name, decorator_name, &block)
24
+ matcher = ->(name, _) { name == column_name.to_s }
25
+ key = "_#{column_name}_#{decorator_name}"
26
+ decorate_matching_attribute_types(matcher, key, &block)
27
+ end
28
+
29
+ # This method is an internal API used to create higher level features like
30
+ # time zone aware attributes.
31
+ #
32
+ # When the schema for a model is loaded, +matcher+ will be called for each
33
+ # attribute with its name and type. If the matcher returns a truthy value,
34
+ # the type will then be yielded to the given block, and the return value
35
+ # of that block will replace the type.
36
+ #
37
+ # Subsequent calls to this method with the same value for +decorator_name+
38
+ # will replace the previous decorator, not decorate twice. This can be
39
+ # used to ensure that class macros are idempotent.
40
+ def decorate_matching_attribute_types(matcher, decorator_name, &block)
41
+ reload_schema_from_cache
42
+ decorator_name = decorator_name.to_s
43
+
44
+ # Create new hashes so we don't modify parent classes
45
+ self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block])
46
+ end
47
+
48
+ private
49
+
50
+ def load_schema!
51
+ super
52
+ attribute_types.each do |name, type|
53
+ decorated_type = attribute_type_decorations.apply(name, type)
54
+ define_attribute(name, decorated_type)
55
+ end
56
+ end
57
+ end
58
+
59
+ class TypeDecorator # :nodoc:
60
+ delegate :clear, to: :@decorations
61
+
62
+ def initialize(decorations = {})
63
+ @decorations = decorations
64
+ end
65
+
66
+ def merge(*args)
67
+ TypeDecorator.new(@decorations.merge(*args))
68
+ end
69
+
70
+ def apply(name, type)
71
+ decorations = decorators_for(name, type)
72
+ decorations.inject(type) do |new_type, block|
73
+ block.call(new_type)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def decorators_for(name, type)
80
+ matching(name, type).map(&:last)
81
+ end
82
+
83
+ def matching(name, type)
84
+ @decorations.values.select do |(matcher, _)|
85
+ matcher.call(name, type)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,330 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mutex_m"
4
+
5
+ module ActiveEntity
6
+ # = Active Entity Attribute Methods
7
+ module AttributeMethods
8
+ extend ActiveSupport::Concern
9
+ include ActiveModel::AttributeMethods
10
+
11
+ included do
12
+ initialize_generated_modules
13
+ include Read
14
+ include Write
15
+ include BeforeTypeCast
16
+ include Query
17
+ include PrimaryKey
18
+ include TimeZoneConversion
19
+ include Serialization
20
+ end
21
+
22
+ RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
23
+
24
+ class GeneratedAttributeMethods < Module #:nodoc:
25
+ include Mutex_m
26
+ end
27
+
28
+ module ClassMethods
29
+ def inherited(child_class) #:nodoc:
30
+ child_class.initialize_generated_modules
31
+ super
32
+ end
33
+
34
+ def initialize_generated_modules # :nodoc:
35
+ @generated_attribute_methods = GeneratedAttributeMethods.new
36
+ @attribute_methods_generated = false
37
+ include @generated_attribute_methods
38
+
39
+ super
40
+ end
41
+
42
+ # Generates all the attribute related methods for columns in the database
43
+ # accessors, mutators and query methods.
44
+ def define_attribute_methods # :nodoc:
45
+ return false if @attribute_methods_generated
46
+ # Use a mutex; we don't want two threads simultaneously trying to define
47
+ # attribute methods.
48
+ generated_attribute_methods.synchronize do
49
+ return false if @attribute_methods_generated
50
+ superclass.define_attribute_methods unless base_class?
51
+ super(attribute_names)
52
+ @attribute_methods_generated = true
53
+ end
54
+ end
55
+
56
+ def undefine_attribute_methods # :nodoc:
57
+ generated_attribute_methods.synchronize do
58
+ super if defined?(@attribute_methods_generated) && @attribute_methods_generated
59
+ @attribute_methods_generated = false
60
+ end
61
+ end
62
+
63
+ # Raises an ActiveEntity::DangerousAttributeError exception when an
64
+ # \Active \Record method is defined in the model, otherwise +false+.
65
+ #
66
+ # class Person < ActiveEntity::Base
67
+ # def save
68
+ # 'already defined by Active Entity'
69
+ # end
70
+ # end
71
+ #
72
+ # Person.instance_method_already_implemented?(:save)
73
+ # # => 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.
74
+ #
75
+ # Person.instance_method_already_implemented?(:name)
76
+ # # => false
77
+ def instance_method_already_implemented?(method_name)
78
+ if dangerous_attribute_method?(method_name)
79
+ 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."
80
+ end
81
+
82
+ if superclass == Base
83
+ super
84
+ else
85
+ # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
86
+ # defines its own attribute method, then we don't want to overwrite that.
87
+ defined = method_defined_within?(method_name, superclass, Base) &&
88
+ ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
89
+ defined || super
90
+ end
91
+ end
92
+
93
+ # A method name is 'dangerous' if it is already (re)defined by Active Entity, but
94
+ # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
95
+ def dangerous_attribute_method?(name) # :nodoc:
96
+ method_defined_within?(name, Base)
97
+ end
98
+
99
+ def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
100
+ if klass.method_defined?(name) || klass.private_method_defined?(name)
101
+ if superklass.method_defined?(name) || superklass.private_method_defined?(name)
102
+ klass.instance_method(name).owner != superklass.instance_method(name).owner
103
+ else
104
+ true
105
+ end
106
+ else
107
+ false
108
+ end
109
+ end
110
+
111
+ # A class method is 'dangerous' if it is already (re)defined by Active Entity, but
112
+ # not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
113
+ def dangerous_class_method?(method_name)
114
+ RESTRICTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
115
+ end
116
+
117
+ def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
118
+ if klass.respond_to?(name, true)
119
+ if superklass.respond_to?(name, true)
120
+ klass.method(name).owner != superklass.method(name).owner
121
+ else
122
+ true
123
+ end
124
+ else
125
+ false
126
+ end
127
+ end
128
+
129
+ # Returns an array of column names as strings if it's not an abstract class and
130
+ # table exists. Otherwise it returns an empty array.
131
+ #
132
+ # class Person < ActiveEntity::Base
133
+ # end
134
+ #
135
+ # Person.attribute_names
136
+ # # => ["id", "created_at", "updated_at", "name", "age"]
137
+ def attribute_names
138
+ @attribute_names ||= if !abstract_class?
139
+ attribute_types.keys
140
+ else
141
+ []
142
+ end
143
+ end
144
+
145
+ # Returns true if the given attribute exists, otherwise false.
146
+ #
147
+ # class Person < ActiveEntity::Base
148
+ # end
149
+ #
150
+ # Person.has_attribute?('name') # => true
151
+ # Person.has_attribute?(:age) # => true
152
+ # Person.has_attribute?(:nothing) # => false
153
+ def has_attribute?(attr_name)
154
+ attribute_types.key?(attr_name.to_s)
155
+ end
156
+ end
157
+
158
+ # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
159
+ #
160
+ # class Person < ActiveEntity::Base
161
+ # end
162
+ #
163
+ # person = Person.new
164
+ # person.has_attribute?(:name) # => true
165
+ # person.has_attribute?('age') # => true
166
+ # person.has_attribute?(:nothing) # => false
167
+ def has_attribute?(attr_name)
168
+ @attributes.key?(attr_name.to_s)
169
+ end
170
+
171
+ # Returns an array of names for the attributes available on this object.
172
+ #
173
+ # class Person < ActiveEntity::Base
174
+ # end
175
+ #
176
+ # person = Person.new
177
+ # person.attribute_names
178
+ # # => ["id", "created_at", "updated_at", "name", "age"]
179
+ def attribute_names
180
+ @attributes.keys
181
+ end
182
+
183
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
184
+ #
185
+ # class Person < ActiveEntity::Base
186
+ # end
187
+ #
188
+ # person = Person.create(name: 'Francesco', age: 22)
189
+ # person.attributes
190
+ # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
191
+ def attributes
192
+ @attributes.to_hash
193
+ end
194
+
195
+ # Returns an <tt>#inspect</tt>-like string for the value of the
196
+ # attribute +attr_name+. String attributes are truncated up to 50
197
+ # characters, Date and Time attributes are returned in the
198
+ # <tt>:db</tt> format. Other attributes return the value of
199
+ # <tt>#inspect</tt> without modification.
200
+ #
201
+ # person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
202
+ #
203
+ # person.attribute_for_inspect(:name)
204
+ # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
205
+ #
206
+ # person.attribute_for_inspect(:created_at)
207
+ # # => "\"2012-10-22 00:15:07\""
208
+ #
209
+ # person.attribute_for_inspect(:tag_ids)
210
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
211
+ def attribute_for_inspect(attr_name)
212
+ value = _read_attribute(attr_name)
213
+ format_for_inspect(value)
214
+ end
215
+
216
+ # Returns +true+ if the specified +attribute+ has been set by the user or by a
217
+ # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
218
+ # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
219
+ # Note that it always returns +true+ with boolean attributes.
220
+ #
221
+ # class Task < ActiveEntity::Base
222
+ # end
223
+ #
224
+ # task = Task.new(title: '', is_done: false)
225
+ # task.attribute_present?(:title) # => false
226
+ # task.attribute_present?(:is_done) # => true
227
+ # task.title = 'Buy milk'
228
+ # task.is_done = true
229
+ # task.attribute_present?(:title) # => true
230
+ # task.attribute_present?(:is_done) # => true
231
+ def attribute_present?(attribute)
232
+ value = _read_attribute(attribute)
233
+ !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
234
+ end
235
+
236
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
237
+ # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
238
+ # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
239
+ #
240
+ # Note: +:id+ is always present.
241
+ #
242
+ # class Person < ActiveEntity::Base
243
+ # belongs_to :organization
244
+ # end
245
+ #
246
+ # person = Person.new(name: 'Francesco', age: '22')
247
+ # person[:name] # => "Francesco"
248
+ # person[:age] # => 22
249
+ #
250
+ # person = Person.select('id').first
251
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
252
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
253
+ def [](attr_name)
254
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
255
+ end
256
+
257
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
258
+ # (Alias for the protected #write_attribute method).
259
+ #
260
+ # class Person < ActiveEntity::Base
261
+ # end
262
+ #
263
+ # person = Person.new
264
+ # person[:age] = '22'
265
+ # person[:age] # => 22
266
+ # person[:age].class # => Integer
267
+ def []=(attr_name, value)
268
+ write_attribute(attr_name, value)
269
+ end
270
+
271
+ # Returns the name of all database fields which have been read from this
272
+ # model. This can be useful in development mode to determine which fields
273
+ # need to be selected. For performance critical pages, selecting only the
274
+ # required fields can be an easy performance win (assuming you aren't using
275
+ # all of the fields on the model).
276
+ #
277
+ # For example:
278
+ #
279
+ # class PostsController < ActionController::Base
280
+ # after_action :print_accessed_fields, only: :index
281
+ #
282
+ # def index
283
+ # @posts = Post.all
284
+ # end
285
+ #
286
+ # private
287
+ #
288
+ # def print_accessed_fields
289
+ # p @posts.first.accessed_fields
290
+ # end
291
+ # end
292
+ #
293
+ # Which allows you to quickly change your code to:
294
+ #
295
+ # class PostsController < ActionController::Base
296
+ # def index
297
+ # @posts = Post.select(:id, :title, :author_id, :updated_at)
298
+ # end
299
+ # end
300
+ def accessed_fields
301
+ @attributes.accessed
302
+ end
303
+
304
+ private
305
+ def attribute_method?(attr_name)
306
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
307
+ defined?(@attributes) && @attributes.key?(attr_name)
308
+ end
309
+
310
+ def attributes_with_values(attribute_names)
311
+ attribute_names.each_with_object({}) do |name, attrs|
312
+ attrs[name] = _read_attribute(name)
313
+ end
314
+ end
315
+
316
+ def format_for_inspect(value)
317
+ if value.is_a?(String) && value.length > 50
318
+ "#{value[0, 50]}...".inspect
319
+ elsif value.is_a?(Date) || value.is_a?(Time)
320
+ %("#{value.to_s(:db)}")
321
+ else
322
+ value.inspect
323
+ end
324
+ end
325
+
326
+ def readonly_attribute?(name)
327
+ self.class.readonly_attributes.include?(name)
328
+ end
329
+ end
330
+ end