activeentity 0.0.1.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +42 -0
- data/README.md +145 -0
- data/Rakefile +29 -0
- data/lib/active_entity.rb +73 -0
- data/lib/active_entity/aggregations.rb +276 -0
- data/lib/active_entity/associations.rb +146 -0
- data/lib/active_entity/associations/embedded/association.rb +134 -0
- data/lib/active_entity/associations/embedded/builder/association.rb +100 -0
- data/lib/active_entity/associations/embedded/builder/collection_association.rb +69 -0
- data/lib/active_entity/associations/embedded/builder/embedded_in.rb +38 -0
- data/lib/active_entity/associations/embedded/builder/embeds_many.rb +13 -0
- data/lib/active_entity/associations/embedded/builder/embeds_one.rb +16 -0
- data/lib/active_entity/associations/embedded/builder/singular_association.rb +28 -0
- data/lib/active_entity/associations/embedded/collection_association.rb +188 -0
- data/lib/active_entity/associations/embedded/collection_proxy.rb +310 -0
- data/lib/active_entity/associations/embedded/embedded_in_association.rb +31 -0
- data/lib/active_entity/associations/embedded/embeds_many_association.rb +15 -0
- data/lib/active_entity/associations/embedded/embeds_one_association.rb +19 -0
- data/lib/active_entity/associations/embedded/singular_association.rb +35 -0
- data/lib/active_entity/attribute_assignment.rb +85 -0
- data/lib/active_entity/attribute_decorators.rb +90 -0
- data/lib/active_entity/attribute_methods.rb +330 -0
- data/lib/active_entity/attribute_methods/before_type_cast.rb +78 -0
- data/lib/active_entity/attribute_methods/primary_key.rb +98 -0
- data/lib/active_entity/attribute_methods/query.rb +35 -0
- data/lib/active_entity/attribute_methods/read.rb +47 -0
- data/lib/active_entity/attribute_methods/serialization.rb +90 -0
- data/lib/active_entity/attribute_methods/time_zone_conversion.rb +91 -0
- data/lib/active_entity/attribute_methods/write.rb +63 -0
- data/lib/active_entity/attributes.rb +165 -0
- data/lib/active_entity/base.rb +303 -0
- data/lib/active_entity/coders/json.rb +15 -0
- data/lib/active_entity/coders/yaml_column.rb +50 -0
- data/lib/active_entity/core.rb +281 -0
- data/lib/active_entity/define_callbacks.rb +17 -0
- data/lib/active_entity/enum.rb +234 -0
- data/lib/active_entity/errors.rb +80 -0
- data/lib/active_entity/gem_version.rb +17 -0
- data/lib/active_entity/inheritance.rb +278 -0
- data/lib/active_entity/integration.rb +78 -0
- data/lib/active_entity/locale/en.yml +45 -0
- data/lib/active_entity/model_schema.rb +115 -0
- data/lib/active_entity/nested_attributes.rb +592 -0
- data/lib/active_entity/readonly_attributes.rb +47 -0
- data/lib/active_entity/reflection.rb +441 -0
- data/lib/active_entity/serialization.rb +25 -0
- data/lib/active_entity/store.rb +242 -0
- data/lib/active_entity/translation.rb +24 -0
- data/lib/active_entity/type.rb +73 -0
- data/lib/active_entity/type/date.rb +9 -0
- data/lib/active_entity/type/date_time.rb +9 -0
- data/lib/active_entity/type/decimal_without_scale.rb +15 -0
- data/lib/active_entity/type/hash_lookup_type_map.rb +25 -0
- data/lib/active_entity/type/internal/timezone.rb +17 -0
- data/lib/active_entity/type/json.rb +30 -0
- data/lib/active_entity/type/modifiers/array.rb +72 -0
- data/lib/active_entity/type/registry.rb +92 -0
- data/lib/active_entity/type/serialized.rb +71 -0
- data/lib/active_entity/type/text.rb +11 -0
- data/lib/active_entity/type/time.rb +21 -0
- data/lib/active_entity/type/type_map.rb +62 -0
- data/lib/active_entity/type/unsigned_integer.rb +17 -0
- data/lib/active_entity/validate_embedded_association.rb +305 -0
- data/lib/active_entity/validations.rb +50 -0
- data/lib/active_entity/validations/absence.rb +25 -0
- data/lib/active_entity/validations/associated.rb +60 -0
- data/lib/active_entity/validations/length.rb +26 -0
- data/lib/active_entity/validations/presence.rb +68 -0
- data/lib/active_entity/validations/subset.rb +76 -0
- data/lib/active_entity/validations/uniqueness_in_embedding.rb +99 -0
- data/lib/active_entity/version.rb +10 -0
- data/lib/tasks/active_entity_tasks.rake +6 -0
- metadata +155 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveEntity::Associations::Embedded::Builder # :nodoc:
|
4
|
+
class EmbeddedIn < SingularAssociation #:nodoc:
|
5
|
+
def self.macro
|
6
|
+
:embedded_in
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.valid_options(options)
|
10
|
+
super + [:default]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.define_callbacks(model, reflection)
|
14
|
+
super
|
15
|
+
add_default_callbacks(model, reflection) if reflection.options[:default]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.add_default_callbacks(model, reflection)
|
19
|
+
model.before_validation lambda { |o|
|
20
|
+
o.association(reflection.name).default(&reflection.options[:default])
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.define_validations(model, reflection)
|
25
|
+
if reflection.options.key?(:required)
|
26
|
+
reflection.options[:optional] = !reflection.options.delete(:required)
|
27
|
+
end
|
28
|
+
|
29
|
+
required = !reflection.options[:optional]
|
30
|
+
|
31
|
+
super
|
32
|
+
|
33
|
+
if required
|
34
|
+
model.validates_presence_of reflection.name, message: :required
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveEntity::Associations::Embedded::Builder # :nodoc:
|
4
|
+
class EmbedsMany < CollectionAssociation #:nodoc:
|
5
|
+
def self.macro
|
6
|
+
:embeds_many
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.valid_options(options)
|
10
|
+
super + [:inverse_of, :index_errors]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveEntity::Associations::Embedded::Builder # :nodoc:
|
4
|
+
class EmbedsOne < SingularAssociation #:nodoc:
|
5
|
+
def self.macro
|
6
|
+
:embeds_one
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.define_validations(model, reflection)
|
10
|
+
super
|
11
|
+
if reflection.options[:required]
|
12
|
+
model.validates_presence_of reflection.name, message: :required
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class is inherited by the has_one and belongs_to association classes
|
4
|
+
|
5
|
+
module ActiveEntity::Associations::Embedded::Builder # :nodoc:
|
6
|
+
class SingularAssociation < Association #:nodoc:
|
7
|
+
def self.valid_options(options)
|
8
|
+
super + [:inverse_of, :required]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.define_accessors(model, reflection)
|
12
|
+
super
|
13
|
+
mixin = model.generated_association_methods
|
14
|
+
name = reflection.name
|
15
|
+
|
16
|
+
define_constructors(mixin, name) if reflection.constructable?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Defines the (build|create)_association methods for belongs_to or has_one association
|
20
|
+
def self.define_constructors(mixin, name)
|
21
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
22
|
+
def build_#{name}(*args, &block)
|
23
|
+
association(:#{name}).build(*args, &block)
|
24
|
+
end
|
25
|
+
CODE
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveEntity
|
4
|
+
module Associations
|
5
|
+
module Embedded
|
6
|
+
# = Active Entity Association Collection
|
7
|
+
#
|
8
|
+
# CollectionAssociation is an abstract class that provides common stuff to
|
9
|
+
# ease the implementation of association proxies that represent
|
10
|
+
# collections. See the class hierarchy in Association.
|
11
|
+
#
|
12
|
+
# CollectionAssociation:
|
13
|
+
# HasManyAssociation => has_many
|
14
|
+
# HasManyThroughAssociation + ThroughAssociation => has_many :through
|
15
|
+
#
|
16
|
+
# The CollectionAssociation class provides common methods to the collections
|
17
|
+
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
|
18
|
+
# the +:through association+ option.
|
19
|
+
#
|
20
|
+
# You need to be careful with assumptions regarding the target: The proxy
|
21
|
+
# does not fetch records from the database until it needs them, but new
|
22
|
+
# ones created with +build+ are added to the target. So, the target may be
|
23
|
+
# non-empty and still lack children waiting to be read from the database.
|
24
|
+
# If you look directly to the database you cannot assume that's the entire
|
25
|
+
# collection because new records may have been added to the target, etc.
|
26
|
+
#
|
27
|
+
# If you need to work on all current children, new and existing records,
|
28
|
+
# +load_target+ and the +loaded+ flag are your friends.
|
29
|
+
class CollectionAssociation < Association #:nodoc:
|
30
|
+
def initialize(owner, reflection)
|
31
|
+
super
|
32
|
+
|
33
|
+
@target = []
|
34
|
+
end
|
35
|
+
|
36
|
+
# Implements the reader method, e.g. foo.items for Foo.has_many :items
|
37
|
+
def reader
|
38
|
+
@proxy ||= CollectionProxy.new(klass, self)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
|
42
|
+
def writer(records)
|
43
|
+
replace(records)
|
44
|
+
end
|
45
|
+
|
46
|
+
def find(&block)
|
47
|
+
target.find(&block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def build(attributes = {}, &block)
|
51
|
+
if attributes.is_a?(Array)
|
52
|
+
attributes.collect { |attr| build(attr, &block) }
|
53
|
+
else
|
54
|
+
add_to_target(build_record(attributes, &block))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add +records+ to this association. Returns +self+ so method calls may
|
59
|
+
# be chained. Since << flattens its argument list and inserts each record,
|
60
|
+
# +push+ and +concat+ behave identically.
|
61
|
+
def concat(*records)
|
62
|
+
records = records.flatten
|
63
|
+
concat_records(records)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Removes all records from the association without calling callbacks
|
67
|
+
# on the associated records. It honors the +:dependent+ option. However
|
68
|
+
# if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
|
69
|
+
# deletion strategy for the association is applied.
|
70
|
+
#
|
71
|
+
# You can force a particular deletion strategy by passing a parameter.
|
72
|
+
#
|
73
|
+
# Example:
|
74
|
+
#
|
75
|
+
# @author.books.delete_all(:nullify)
|
76
|
+
# @author.books.delete_all(:delete_all)
|
77
|
+
#
|
78
|
+
# See delete for more info.
|
79
|
+
def delete_all
|
80
|
+
target.clear
|
81
|
+
end
|
82
|
+
alias destroy_all delete_all
|
83
|
+
|
84
|
+
# Removes +records+ from this association calling +before_remove+ and
|
85
|
+
# +after_remove+ callbacks.
|
86
|
+
#
|
87
|
+
# This method is abstract in the sense that +delete_records+ has to be
|
88
|
+
# provided by descendants. Note this method does not imply the records
|
89
|
+
# are actually removed from the database, that depends precisely on
|
90
|
+
# +delete_records+. They are in any case removed from the collection.
|
91
|
+
def delete(*records)
|
92
|
+
records.each do |record|
|
93
|
+
target.delete(record)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
alias destroy delete
|
97
|
+
|
98
|
+
# Returns the size of the collection by executing a SELECT COUNT(*)
|
99
|
+
# query if the collection hasn't been loaded, and calling
|
100
|
+
# <tt>collection.size</tt> if it has.
|
101
|
+
#
|
102
|
+
# If the collection has been already loaded +size+ and +length+ are
|
103
|
+
# equivalent. If not and you are going to need the records anyway
|
104
|
+
# +length+ will take one less query. Otherwise +size+ is more efficient.
|
105
|
+
#
|
106
|
+
# This method is abstract in the sense that it relies on
|
107
|
+
# +count_records+, which is a method descendants have to provide.
|
108
|
+
def size
|
109
|
+
target.size
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns true if the collection is empty.
|
113
|
+
#
|
114
|
+
# If the collection has been loaded
|
115
|
+
# it is equivalent to <tt>collection.size.zero?</tt>. If the
|
116
|
+
# collection has not been loaded, it is equivalent to
|
117
|
+
# <tt>collection.exists?</tt>. If the collection has not already been
|
118
|
+
# loaded and you are going to fetch the records anyway it is better to
|
119
|
+
# check <tt>collection.length.zero?</tt>.
|
120
|
+
def empty?
|
121
|
+
size.zero?
|
122
|
+
end
|
123
|
+
|
124
|
+
# Replace this collection with +other_array+. This will perform a diff
|
125
|
+
# and delete/add only records that have changed.
|
126
|
+
def replace(other_array)
|
127
|
+
other_array.each { |val| raise_on_type_mismatch!(val) }
|
128
|
+
|
129
|
+
target.replace other_array
|
130
|
+
end
|
131
|
+
|
132
|
+
def include?(record)
|
133
|
+
if record.is_a?(reflection.klass)
|
134
|
+
target.include?(record)
|
135
|
+
else
|
136
|
+
false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def add_to_target(record, skip_callbacks = false, &block)
|
141
|
+
# index = @target.index(record)
|
142
|
+
# replace_on_target(record, index, skip_callbacks, &block)
|
143
|
+
replace_on_target(record, nil, skip_callbacks, &block)
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def concat_records(records)
|
149
|
+
records.each do |record|
|
150
|
+
raise_on_type_mismatch!(record)
|
151
|
+
add_to_target(record)
|
152
|
+
end
|
153
|
+
|
154
|
+
records
|
155
|
+
end
|
156
|
+
|
157
|
+
def replace_on_target(record, index, skip_callbacks)
|
158
|
+
callback(:before_add, record) unless skip_callbacks
|
159
|
+
|
160
|
+
set_inverse_instance(record)
|
161
|
+
|
162
|
+
yield(record) if block_given?
|
163
|
+
|
164
|
+
if index
|
165
|
+
target[index] = record
|
166
|
+
else
|
167
|
+
target << record
|
168
|
+
end
|
169
|
+
|
170
|
+
callback(:after_add, record) unless skip_callbacks
|
171
|
+
|
172
|
+
record
|
173
|
+
end
|
174
|
+
|
175
|
+
def callback(method, record)
|
176
|
+
callbacks_for(method).each do |callback|
|
177
|
+
callback.call(method, owner, record)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def callbacks_for(callback_name)
|
182
|
+
full_callback_name = "#{callback_name}_for_#{reflection.name}"
|
183
|
+
owner.class.send(full_callback_name)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveEntity
|
4
|
+
module Associations
|
5
|
+
module Embedded
|
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, 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
|