all_futures 1.0.2 → 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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -1
  3. data/Gemfile.lock +297 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +59 -77
  6. data/Rakefile +13 -1
  7. data/SUMMARY.md +22 -2
  8. data/bin/console +8 -0
  9. data/bin/loc +0 -0
  10. data/bin/setup +0 -0
  11. data/bin/standardize +0 -0
  12. data/lib/all_futures/association.rb +42 -0
  13. data/lib/all_futures/association_index.rb +69 -0
  14. data/lib/all_futures/attributes.rb +28 -0
  15. data/lib/all_futures/base.rb +66 -0
  16. data/lib/all_futures/callbacks.rb +71 -0
  17. data/lib/all_futures/configuration.rb +9 -0
  18. data/lib/all_futures/dirty.rb +116 -0
  19. data/lib/all_futures/embeds.rb +40 -0
  20. data/lib/all_futures/errors.rb +37 -0
  21. data/lib/all_futures/finder.rb +214 -0
  22. data/lib/all_futures/locale/en.yml +49 -0
  23. data/lib/all_futures/persistence.rb +595 -0
  24. data/lib/all_futures/presenter.rb +35 -0
  25. data/lib/all_futures/railtie.rb +26 -0
  26. data/lib/all_futures/translation.rb +22 -0
  27. data/lib/all_futures/validations.rb +10 -0
  28. data/lib/all_futures/version.rb +3 -1
  29. data/lib/all_futures/versions.rb +80 -0
  30. data/lib/all_futures.rb +24 -1
  31. data/lib/vendor/ACTIVE_ENTITY_MIT-LICENSE +42 -0
  32. data/lib/vendor/README.md +8 -0
  33. data/lib/vendor/active_entity/aggregations.rb +277 -0
  34. data/lib/vendor/active_entity/associations/embeds/association.rb +134 -0
  35. data/lib/vendor/active_entity/associations/embeds/builder/association.rb +105 -0
  36. data/lib/vendor/active_entity/associations/embeds/builder/collection_association.rb +71 -0
  37. data/lib/vendor/active_entity/associations/embeds/builder/embedded_in.rb +38 -0
  38. data/lib/vendor/active_entity/associations/embeds/builder/embeds_many.rb +13 -0
  39. data/lib/vendor/active_entity/associations/embeds/builder/embeds_one.rb +16 -0
  40. data/lib/vendor/active_entity/associations/embeds/builder/singular_association.rb +28 -0
  41. data/lib/vendor/active_entity/associations/embeds/collection_association.rb +209 -0
  42. data/lib/vendor/active_entity/associations/embeds/collection_proxy.rb +310 -0
  43. data/lib/vendor/active_entity/associations/embeds/embedded_in_association.rb +31 -0
  44. data/lib/vendor/active_entity/associations/embeds/embeds_many_association.rb +15 -0
  45. data/lib/vendor/active_entity/associations/embeds/embeds_one_association.rb +26 -0
  46. data/lib/vendor/active_entity/associations/embeds/singular_association.rb +35 -0
  47. data/lib/vendor/active_entity/associations.rb +168 -0
  48. data/lib/vendor/active_entity/attribute_assignment.rb +87 -0
  49. data/lib/vendor/active_entity/attribute_decorators.rb +90 -0
  50. data/lib/vendor/active_entity/attribute_methods/before_type_cast.rb +78 -0
  51. data/lib/vendor/active_entity/attribute_methods/dirty.rb +180 -0
  52. data/lib/vendor/active_entity/attribute_methods/primary_key.rb +96 -0
  53. data/lib/vendor/active_entity/attribute_methods/query.rb +38 -0
  54. data/lib/vendor/active_entity/attribute_methods/read.rb +44 -0
  55. data/lib/vendor/active_entity/attribute_methods/serialization.rb +90 -0
  56. data/lib/vendor/active_entity/attribute_methods/time_zone_conversion.rb +93 -0
  57. data/lib/vendor/active_entity/attribute_methods/write.rb +54 -0
  58. data/lib/vendor/active_entity/attribute_methods.rb +833 -0
  59. data/lib/vendor/active_entity/attributes.rb +240 -0
  60. data/lib/vendor/active_entity/base.rb +293 -0
  61. data/lib/vendor/active_entity/callbacks.rb +21 -0
  62. data/lib/vendor/active_entity/coders/json.rb +15 -0
  63. data/lib/vendor/active_entity/coders/yaml_column.rb +50 -0
  64. data/lib/vendor/active_entity/core.rb +348 -0
  65. data/lib/vendor/active_entity/enum.rb +260 -0
  66. data/lib/vendor/active_entity/errors.rb +69 -0
  67. data/lib/vendor/active_entity/gem_version.rb +17 -0
  68. data/lib/vendor/active_entity/inheritance.rb +176 -0
  69. data/lib/vendor/active_entity/integration.rb +78 -0
  70. data/lib/vendor/active_entity/locale/en.yml +45 -0
  71. data/lib/vendor/active_entity/model_schema.rb +118 -0
  72. data/lib/vendor/active_entity/nested_attributes.rb +554 -0
  73. data/lib/vendor/active_entity/persistence.rb +20 -0
  74. data/lib/vendor/active_entity/railtie.rb +76 -0
  75. data/lib/vendor/active_entity/readonly_attributes.rb +55 -0
  76. data/lib/vendor/active_entity/reflection.rb +447 -0
  77. data/lib/vendor/active_entity/serialization.rb +28 -0
  78. data/lib/vendor/active_entity/store.rb +291 -0
  79. data/lib/vendor/active_entity/translation.rb +24 -0
  80. data/lib/vendor/active_entity/type/date.rb +9 -0
  81. data/lib/vendor/active_entity/type/date_time.rb +9 -0
  82. data/lib/vendor/active_entity/type/internal/timezone.rb +17 -0
  83. data/lib/vendor/active_entity/type/json.rb +30 -0
  84. data/lib/vendor/active_entity/type/modifiers/array.rb +72 -0
  85. data/lib/vendor/active_entity/type/modifiers/array_without_blank.rb +19 -0
  86. data/lib/vendor/active_entity/type/registry.rb +112 -0
  87. data/lib/vendor/active_entity/type/serialized.rb +71 -0
  88. data/lib/vendor/active_entity/type/text.rb +11 -0
  89. data/lib/vendor/active_entity/type/time.rb +21 -0
  90. data/lib/vendor/active_entity/type/unsigned_integer.rb +17 -0
  91. data/lib/vendor/active_entity/type.rb +75 -0
  92. data/lib/vendor/active_entity/validate_embeds_association.rb +300 -0
  93. data/lib/vendor/active_entity/validations/absence.rb +25 -0
  94. data/lib/vendor/active_entity/validations/associated.rb +60 -0
  95. data/lib/vendor/active_entity/validations/length.rb +26 -0
  96. data/lib/vendor/active_entity/validations/presence.rb +68 -0
  97. data/lib/vendor/active_entity/validations/subset.rb +76 -0
  98. data/lib/vendor/active_entity/validations/uniqueness_in_embeds.rb +91 -0
  99. data/lib/vendor/active_entity/validations/uniqueness_on_active_record.rb +234 -0
  100. data/lib/vendor/active_entity/validations.rb +47 -0
  101. data/lib/vendor/active_entity/version.rb +10 -0
  102. data/lib/vendor/active_entity.rb +99 -0
  103. data/lib/vendor/core_ext/array_without_blank.rb +48 -0
  104. metadata +172 -31
  105. data/lib/all_futures/all_futures.rb +0 -28
  106. data/lib/all_futures/config.rb +0 -14
@@ -0,0 +1,310 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embeds
6
+ # Association proxies in Active Entity are middlemen between the object that
7
+ # holds the association, known as the <tt>@owner</tt>, and the actual associated
8
+ # object, known as the <tt>@target</tt>. The kind of association any proxy is
9
+ # about is available in <tt>@reflection</tt>. That's an instance of the class
10
+ # ActiveEntity::Reflection::AssociationReflection.
11
+ #
12
+ # For example, given
13
+ #
14
+ # class Blog < ActiveEntity::Base
15
+ # has_many :posts
16
+ # end
17
+ #
18
+ # blog = Blog.first
19
+ #
20
+ # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
21
+ # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
22
+ # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
23
+ #
24
+ # This class delegates unknown methods to <tt>@target</tt> via
25
+ # <tt>method_missing</tt>.
26
+ #
27
+ # The <tt>@target</tt> object is not \loaded until needed. For example,
28
+ #
29
+ # blog.posts.count
30
+ #
31
+ # is computed directly through SQL and does not trigger by itself the
32
+ # instantiation of the actual post records.
33
+ class CollectionProxy
34
+ include Enumerable
35
+
36
+ attr_reader :klass
37
+ alias :model :klass
38
+
39
+ delegate :to_xml, :encode_with, :length, :each, :join,
40
+ :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
41
+ :find, :last, :take, :blank?, :present?, :empty?, :any?, :one?, :many?, :include?,
42
+ :to_sentence, :to_formatted_s, :as_json,
43
+ :shuffle, :split, :slice, :index, :rindex, :size, to: :records
44
+
45
+ def initialize(klass, association)
46
+ @klass = klass
47
+
48
+ @association = association
49
+
50
+ extensions = association.extensions
51
+ extend(*extensions) if extensions.any?
52
+ end
53
+
54
+ # Initializes new record from relation while maintaining the current
55
+ # scope.
56
+ #
57
+ # Expects arguments in the same format as {ActiveEntity::Base.new}[rdoc-ref:Core.new].
58
+ #
59
+ # users = User.where(name: 'DHH')
60
+ # user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
61
+ #
62
+ # You can also pass a block to new with the new record as argument:
63
+ #
64
+ # user = users.new { |user| user.name = 'Oscar' }
65
+ # user.name # => Oscar
66
+ def new(attributes = nil, &block)
67
+ klass.new(attributes, &block)
68
+ end
69
+
70
+ alias build new
71
+
72
+ def pretty_print(q)
73
+ q.pp(records)
74
+ end
75
+
76
+ def inspect
77
+ entries = records.take([size, 11].compact.min).map!(&:inspect)
78
+
79
+ entries[10] = "..." if entries.size == 11
80
+
81
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
82
+ end
83
+
84
+ # Returns +true+ if the association has been loaded, otherwise +false+.
85
+ #
86
+ # person.pets.loaded? # => false
87
+ # person.pets
88
+ # person.pets.loaded? # => true
89
+ def loaded?
90
+ @association.loaded?
91
+ end
92
+
93
+ # Returns a new object of the collection type that has been instantiated
94
+ # with +attributes+ and linked to this object, but have not yet been saved.
95
+ # You can pass an array of attributes hashes, this will return an array
96
+ # with the new objects.
97
+ #
98
+ # class Person
99
+ # has_many :pets
100
+ # end
101
+ #
102
+ # person.pets.build
103
+ # # => #<Pet id: nil, name: nil, person_id: 1>
104
+ #
105
+ # person.pets.build(name: 'Fancy-Fancy')
106
+ # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
107
+ #
108
+ # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
109
+ # # => [
110
+ # # #<Pet id: nil, name: "Spook", person_id: 1>,
111
+ # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
112
+ # # #<Pet id: nil, name: "Brain", person_id: 1>
113
+ # # ]
114
+ #
115
+ # person.pets.size # => 5 # size of the collection
116
+ # person.pets.count # => 0 # count from database
117
+ def build(attributes = {}, &block)
118
+ @association.build(attributes, &block)
119
+ end
120
+ alias_method :new, :build
121
+
122
+ # Add one or more records to the collection by setting their foreign keys
123
+ # to the association's primary key. Since #<< flattens its argument list and
124
+ # inserts each record, +push+ and #concat behave identically. Returns +self+
125
+ # so method calls may be chained.
126
+ #
127
+ # class Person < ActiveEntity::Base
128
+ # has_many :pets
129
+ # end
130
+ #
131
+ # person.pets.size # => 0
132
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
133
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
134
+ # person.pets.size # => 3
135
+ #
136
+ # person.id # => 1
137
+ # person.pets
138
+ # # => [
139
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
140
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
141
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
142
+ # # ]
143
+ #
144
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
145
+ # person.pets.size # => 5
146
+ def concat(*records)
147
+ @association.concat(*records)
148
+ end
149
+
150
+ # Replaces this collection with +other_array+. This will perform a diff
151
+ # and delete/add only records that have changed.
152
+ #
153
+ # class Person < ActiveEntity::Base
154
+ # has_many :pets
155
+ # end
156
+ #
157
+ # person.pets
158
+ # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
159
+ #
160
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
161
+ #
162
+ # person.pets.replace(other_pets)
163
+ #
164
+ # person.pets
165
+ # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
166
+ #
167
+ # If the supplied array has an incorrect association type, it raises
168
+ # an <tt>ActiveEntity::AssociationTypeMismatch</tt> error:
169
+ #
170
+ # person.pets.replace(["doo", "ggie", "gaga"])
171
+ # # => ActiveEntity::AssociationTypeMismatch: Pet expected, got String
172
+ def replace(other_array)
173
+ @association.replace(other_array)
174
+ end
175
+
176
+ def delete_all
177
+ @association.delete_all
178
+ end
179
+ alias destroy_all delete_all
180
+
181
+ def delete(*records)
182
+ @association.delete(*records)
183
+ end
184
+ alias destroy delete
185
+
186
+ # Equivalent to +delete_all+. The difference is that returns +self+, instead
187
+ # of an array with the deleted objects, so methods can be chained. See
188
+ # +delete_all+ for more information.
189
+ # Note that because +delete_all+ removes records by directly
190
+ # running an SQL query into the database, the +updated_at+ column of
191
+ # the object is not changed.
192
+ def clear
193
+ delete_all
194
+ self
195
+ end
196
+
197
+ def proxy_association
198
+ @association
199
+ end
200
+
201
+ # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
202
+ # contain the same number of elements and if each element is equal
203
+ # to the corresponding element in the +other+ array, otherwise returns
204
+ # +false+.
205
+ #
206
+ # class Person < ActiveEntity::Base
207
+ # has_many :pets
208
+ # end
209
+ #
210
+ # person.pets
211
+ # # => [
212
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
213
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
214
+ # # ]
215
+ #
216
+ # other = person.pets.to_ary
217
+ #
218
+ # person.pets == other
219
+ # # => true
220
+ #
221
+ # other = [Pet.new(id: 1), Pet.new(id: 2)]
222
+ #
223
+ # person.pets == other
224
+ # # => false
225
+ def ==(other)
226
+ records == other
227
+ end
228
+
229
+ ##
230
+ # :method: to_ary
231
+ #
232
+ # :call-seq:
233
+ # to_ary()
234
+ #
235
+ # Returns a new array of objects from the collection. If the collection
236
+ # hasn't been loaded, it fetches the records from the database.
237
+ #
238
+ # class Person < ActiveEntity::Base
239
+ # has_many :pets
240
+ # end
241
+ #
242
+ # person.pets
243
+ # # => [
244
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
245
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
246
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
247
+ # # ]
248
+ #
249
+ # other_pets = person.pets.to_ary
250
+ # # => [
251
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
252
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
253
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
254
+ # # ]
255
+ #
256
+ # other_pets.replace([Pet.new(name: 'BooGoo')])
257
+ #
258
+ # other_pets
259
+ # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
260
+ #
261
+ # person.pets
262
+ # # This is not affected by replace
263
+ # # => [
264
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
265
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
266
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
267
+ # # ]
268
+ # Converts relation objects to Array.
269
+ def to_ary
270
+ records.dup
271
+ end
272
+ alias to_a to_ary
273
+
274
+ def records # :nodoc:
275
+ @association.target
276
+ end
277
+
278
+ # Adds one or more +records+ to the collection by setting their foreign keys
279
+ # to the association's primary key. Returns +self+, so several appends may be
280
+ # chained together.
281
+ #
282
+ # class Person < ActiveEntity::Base
283
+ # has_many :pets
284
+ # end
285
+ #
286
+ # person.pets.size # => 0
287
+ # person.pets << Pet.new(name: 'Fancy-Fancy')
288
+ # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
289
+ # person.pets.size # => 3
290
+ #
291
+ # person.id # => 1
292
+ # person.pets
293
+ # # => [
294
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
295
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
296
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
297
+ # # ]
298
+ def <<(*records)
299
+ proxy_association.concat(records) && self
300
+ end
301
+ alias_method :push, :<<
302
+ alias_method :append, :<<
303
+
304
+ def prepend(*_args)
305
+ raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embeds
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 Embeds
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,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embeds
6
+ # = Active Entity Has One Association
7
+ class EmbedsOneAssociation < SingularAssociation #:nodoc:
8
+ private
9
+
10
+ def replace(record)
11
+ self.target =
12
+ if record.is_a? reflection.klass
13
+ record
14
+ elsif record.nil?
15
+ nil
16
+ elsif record.respond_to?(:to_h)
17
+ build_record(record.to_h)
18
+ end
19
+ rescue => ex
20
+ raise_on_type_mismatch!(record)
21
+ raise ex
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embeds
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,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+ require "active_support/core_ext/string/conversions"
5
+
6
+ module ActiveEntity
7
+ class AssociationNotFoundError < ConfigurationError #:nodoc:
8
+ def initialize(record = nil, association_name = nil)
9
+ if record && association_name
10
+ super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
11
+ else
12
+ super("Association was not found.")
13
+ end
14
+ end
15
+ end
16
+
17
+ class InverseOfAssociationNotFoundError < ActiveEntityError #:nodoc:
18
+ def initialize(reflection = nil, associated_class = nil)
19
+ if reflection
20
+ super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
21
+ else
22
+ super("Could not find the inverse association.")
23
+ end
24
+ end
25
+ end
26
+
27
+ # See ActiveEntity::Associations::ClassMethods for documentation.
28
+ module Associations # :nodoc:
29
+ extend ActiveSupport::Autoload
30
+ extend ActiveSupport::Concern
31
+ # These classes will be loaded when associations are created.
32
+ # So there is no need to eager load them.
33
+ module Embeds
34
+ extend ActiveSupport::Autoload
35
+
36
+ autoload :Association, "active_entity/associations/embeds/association"
37
+ autoload :SingularAssociation, "active_entity/associations/embeds/singular_association"
38
+ autoload :CollectionAssociation, "active_entity/associations/embeds/collection_association"
39
+ autoload :CollectionProxy, "active_entity/associations/embeds/collection_proxy"
40
+
41
+ module Builder #:nodoc:
42
+ autoload :Association, "active_entity/associations/embeds/builder/association"
43
+ autoload :SingularAssociation, "active_entity/associations/embeds/builder/singular_association"
44
+ autoload :CollectionAssociation, "active_entity/associations/embeds/builder/collection_association"
45
+
46
+ autoload :EmbeddedIn, "active_entity/associations/embeds/builder/embedded_in"
47
+ autoload :EmbedsOne, "active_entity/associations/embeds/builder/embeds_one"
48
+ autoload :EmbedsMany, "active_entity/associations/embeds/builder/embeds_many"
49
+ end
50
+
51
+ eager_autoload do
52
+ autoload :EmbeddedInAssociation
53
+ autoload :EmbedsOneAssociation
54
+ autoload :EmbedsManyAssociation
55
+ end
56
+ end
57
+
58
+ def self.eager_load!
59
+ super
60
+ Embeds.eager_load!
61
+ end
62
+ # Returns the association instance for the given name, instantiating it if it doesn't already exist
63
+ def association(name) #:nodoc:
64
+ association = association_instance_get(name)
65
+
66
+ if association.nil?
67
+ unless reflection = self.class._reflect_on_association(name)
68
+ raise AssociationNotFoundError.new(self, name)
69
+ end
70
+ association = reflection.association_class.new(self, reflection)
71
+ association_instance_set(name, association)
72
+ end
73
+
74
+ association
75
+ end
76
+
77
+ def association_cached?(name) # :nodoc:
78
+ @association_cache.key?(name)
79
+ end
80
+
81
+ def initialize_dup(*) # :nodoc:
82
+ @association_cache = {}
83
+ super
84
+ end
85
+
86
+ private
87
+
88
+ def init_internals
89
+ @association_cache = {}
90
+ super
91
+ end
92
+
93
+ # Returns the specified association instance if it exists, +nil+ otherwise.
94
+ def association_instance_get(name)
95
+ @association_cache[name]
96
+ end
97
+
98
+ # Set the specified association instance.
99
+ def association_instance_set(name, association)
100
+ @association_cache[name] = association
101
+ end
102
+
103
+ # \Associations are a set of macro-like class methods for tying objects together through
104
+ # foreign keys. They express relationships like "Project has one Project Manager"
105
+ # or "Project belongs to a Portfolio". Each macro adds a number of methods to the
106
+ # class which are specialized according to the collection or association symbol and the
107
+ # options hash. It works much the same way as Ruby's own <tt>attr*</tt>
108
+ # methods.
109
+ #
110
+ # class Project < ActiveEntity::Base
111
+ # belongs_to :portfolio
112
+ # has_one :project_manager
113
+ # has_many :milestones
114
+ # has_and_belongs_to_many :categories
115
+ # end
116
+ #
117
+ # The project class now has the following methods (and more) to ease the traversal and
118
+ # manipulation of its relationships:
119
+ # * <tt>Project#portfolio</tt>, <tt>Project#portfolio=(portfolio)</tt>, <tt>Project#reload_portfolio</tt>
120
+ # * <tt>Project#project_manager</tt>, <tt>Project#project_manager=(project_manager)</tt>, <tt>Project#reload_project_manager</tt>
121
+ # * <tt>Project#milestones.empty?</tt>, <tt>Project#milestones.size</tt>, <tt>Project#milestones</tt>, <tt>Project#milestones<<(milestone)</tt>,
122
+ # <tt>Project#milestones.delete(milestone)</tt>, <tt>Project#milestones.destroy(milestone)</tt>, <tt>Project#milestones.find(milestone_id)</tt>,
123
+ # <tt>Project#milestones.build</tt>, <tt>Project#milestones.create</tt>
124
+ # * <tt>Project#categories.empty?</tt>, <tt>Project#categories.size</tt>, <tt>Project#categories</tt>, <tt>Project#categories<<(category1)</tt>,
125
+ # <tt>Project#categories.delete(category1)</tt>, <tt>Project#categories.destroy(category1)</tt>
126
+ #
127
+ # === A word of warning
128
+ #
129
+ # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveEntity::Core] of
130
+ # <tt>ActiveEntity::Base</tt>. Since the association adds a method with that name to
131
+ # its model, using an association with the same name as one provided by <tt>ActiveEntity::Base</tt> will override the method inherited through <tt>ActiveEntity::Base</tt> and will break things.
132
+ # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveEntity::Base</tt> instance methods.
133
+ module ClassMethods
134
+ def embedded_in(name, **options)
135
+ reflection = Embeds::Builder::EmbeddedIn.build(self, name, options)
136
+ Reflection.add_reflection self, name, reflection
137
+ end
138
+
139
+ def embeds_one(name, **options)
140
+ reflection = Embeds::Builder::EmbedsOne.build(self, name, options)
141
+ Reflection.add_reflection self, name, reflection
142
+ end
143
+
144
+ def embeds_many(name, **options)
145
+ reflection = Embeds::Builder::EmbedsMany.build(self, name, options)
146
+ Reflection.add_reflection self, name, reflection
147
+ end
148
+
149
+ def association_names
150
+ @association_names ||=
151
+ if !abstract_class?
152
+ reflections.keys.map(&:to_sym)
153
+ else
154
+ []
155
+ end
156
+ end
157
+
158
+ def embeds_association_names
159
+ @association_names ||=
160
+ if !abstract_class?
161
+ reflections.select { |_, r| r.embedded? }.keys.map(&:to_sym)
162
+ else
163
+ []
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,87 @@
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 = nested_parameter_attributes = nil
13
+
14
+ attributes.each do |k, v|
15
+ key = k.to_s
16
+
17
+ if key.include?("(")
18
+ (multi_parameter_attributes ||= {})[key] = v
19
+ elsif v.is_a?(Hash)
20
+ (nested_parameter_attributes ||= {})[key] = v
21
+ else
22
+ _assign_attribute(key, v)
23
+ end
24
+ end
25
+
26
+ assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
27
+ assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
28
+ end
29
+
30
+ # Assign any deferred nested attributes after the base attributes have been set.
31
+ def assign_nested_parameter_attributes(pairs)
32
+ pairs.each { |k, v| _assign_attribute(k, v) }
33
+ end
34
+
35
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
36
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
37
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
38
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
39
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
40
+ # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
41
+ def assign_multiparameter_attributes(pairs)
42
+ execute_callstack_for_multiparameter_attributes(
43
+ extract_callstack_for_multiparameter_attributes(pairs)
44
+ )
45
+ end
46
+
47
+ def execute_callstack_for_multiparameter_attributes(callstack)
48
+ errors = []
49
+ callstack.each do |name, values_with_empty_parameters|
50
+ if values_with_empty_parameters.each_value.all?(&:nil?)
51
+ values = nil
52
+ else
53
+ values = values_with_empty_parameters
54
+ end
55
+ send("#{name}=", values)
56
+ rescue => ex
57
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
58
+ end
59
+ unless errors.empty?
60
+ error_descriptions = errors.map(&:message).join(",")
61
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
62
+ end
63
+ end
64
+
65
+ def extract_callstack_for_multiparameter_attributes(pairs)
66
+ attributes = {}
67
+
68
+ pairs.each do |(multiparameter_name, value)|
69
+ attribute_name = multiparameter_name.split("(").first
70
+ attributes[attribute_name] ||= {}
71
+
72
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
73
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
74
+ end
75
+
76
+ attributes
77
+ end
78
+
79
+ def type_cast_attribute_value(multiparameter_name, value)
80
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
81
+ end
82
+
83
+ def find_parameter_position(multiparameter_name)
84
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
85
+ end
86
+ end
87
+ end