duck_record 0.0.3 → 0.0.5
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/README.md +25 -6
- data/Rakefile +0 -1
- data/lib/duck_record/associations/association.rb +86 -0
- data/lib/duck_record/associations/builder/association.rb +98 -0
- data/lib/duck_record/associations/builder/collection_association.rb +48 -0
- data/lib/duck_record/associations/builder/has_many.rb +7 -0
- data/lib/duck_record/associations/builder/has_one.rb +15 -0
- data/lib/duck_record/associations/builder/singular_association.rb +22 -0
- data/lib/duck_record/associations/collection_association.rb +187 -0
- data/lib/duck_record/associations/collection_proxy.rb +894 -0
- data/lib/duck_record/associations/has_many_association.rb +12 -0
- data/lib/duck_record/associations/has_one_association.rb +12 -0
- data/lib/duck_record/associations/singular_association.rb +39 -0
- data/lib/duck_record/associations.rb +1317 -0
- data/lib/duck_record/base.rb +4 -0
- data/lib/duck_record/errors.rb +8 -0
- data/lib/duck_record/locale/en.yml +1 -5
- data/lib/duck_record/nested_attributes.rb +531 -0
- data/lib/duck_record/nested_validate_association.rb +262 -0
- data/lib/duck_record/reflection.rb +309 -0
- data/lib/duck_record/version.rb +1 -1
- data/lib/duck_record.rb +10 -4
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc1667096dfb88fc6d2b74ea577440eb5bcf8fd0
|
4
|
+
data.tar.gz: 86a738d000265404c2309bf4d04ff6a5db11d446
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e92f601db7fae6b7a687cb42caa647ba39ca2441f0174a6999feeb5a0d1537eab686c7297734981ca2bb77109b3eaf7f7a5cff7a1da63f2bff1ef6efe15f2a9
|
7
|
+
data.tar.gz: e61511a9b89654b61a1350d69a748e44cba5b4ef8fd3ef061667db04f12a1d57e00d1a6ac95dfa63a8c32ddf512d7b146c7432a1f0a7f9ea6ba7937dbd5c9977
|
data/README.md
CHANGED
@@ -7,19 +7,39 @@ Actually it's extract from Active Record.
|
|
7
7
|
## Usage
|
8
8
|
|
9
9
|
```ruby
|
10
|
+
class Person < DuckRecord::Base
|
11
|
+
attribute :name, :string
|
12
|
+
attribute :age, :integer
|
13
|
+
|
14
|
+
validates :name, presence: true
|
15
|
+
end
|
16
|
+
|
17
|
+
class Comment < DuckRecord::Base
|
18
|
+
attribute :content, :string
|
19
|
+
|
20
|
+
validates :content, presence: true
|
21
|
+
end
|
22
|
+
|
10
23
|
class Book < DuckRecord::Base
|
24
|
+
has_one :author, class_name: 'Person', validate: true
|
25
|
+
accepts_nested_attributes_for :author
|
26
|
+
|
27
|
+
has_many :comments, validate: true
|
28
|
+
accepts_nested_attributes_for :comments
|
29
|
+
|
11
30
|
attribute :title, :string
|
31
|
+
attribute :tags, :string, array: true
|
12
32
|
attribute :price, :decimal, default: 0
|
33
|
+
attribute :meta, :json, default: {}
|
13
34
|
attribute :bought_at, :datetime, default: -> { Time.new }
|
14
35
|
|
15
|
-
# some types that cheated from PG
|
16
|
-
attribute :tags, :string, array: true
|
17
|
-
attribute :meta, :json, default: {}
|
18
|
-
|
19
36
|
validates :title, presence: true
|
20
37
|
end
|
21
38
|
```
|
22
39
|
|
40
|
+
then use these models like a Active Record model,
|
41
|
+
but remember that can't be persisting!
|
42
|
+
|
23
43
|
## Installation
|
24
44
|
|
25
45
|
Since Duck Record is under early development,
|
@@ -43,12 +63,11 @@ $ gem install duck_record
|
|
43
63
|
|
44
64
|
## TODO
|
45
65
|
|
46
|
-
- `has_one`, `has_many`
|
47
66
|
- refactor that original design for database
|
48
67
|
- update docs
|
49
68
|
- add useful methods
|
50
69
|
- add tests
|
51
|
-
- let me
|
70
|
+
- let me know..
|
52
71
|
|
53
72
|
## Contributing
|
54
73
|
|
data/Rakefile
CHANGED
@@ -0,0 +1,86 @@
|
|
1
|
+
require "active_support/core_ext/array/wrap"
|
2
|
+
|
3
|
+
module DuckRecord
|
4
|
+
module Associations
|
5
|
+
# = Active Record Associations
|
6
|
+
#
|
7
|
+
# This is the root class of all associations ('+ Foo' signifies an included module Foo):
|
8
|
+
#
|
9
|
+
# Association
|
10
|
+
# SingularAssociation
|
11
|
+
# HasOneAssociation + ForeignAssociation
|
12
|
+
# HasOneThroughAssociation + ThroughAssociation
|
13
|
+
# BelongsToAssociation
|
14
|
+
# BelongsToPolymorphicAssociation
|
15
|
+
# CollectionAssociation
|
16
|
+
# HasManyAssociation + ForeignAssociation
|
17
|
+
# HasManyThroughAssociation + ThroughAssociation
|
18
|
+
class Association #:nodoc:
|
19
|
+
attr_reader :owner, :target, :reflection
|
20
|
+
|
21
|
+
delegate :options, to: :reflection
|
22
|
+
|
23
|
+
def initialize(owner, reflection)
|
24
|
+
reflection.check_validity!
|
25
|
+
|
26
|
+
@owner, @reflection = owner, reflection
|
27
|
+
|
28
|
+
reset
|
29
|
+
end
|
30
|
+
|
31
|
+
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
32
|
+
def reset
|
33
|
+
@target = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
|
37
|
+
def target=(target)
|
38
|
+
@target = target
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
|
42
|
+
# polymorphic_type field on the owner.
|
43
|
+
def klass
|
44
|
+
reflection.klass
|
45
|
+
end
|
46
|
+
|
47
|
+
# We can't dump @reflection since it contains the scope proc
|
48
|
+
def marshal_dump
|
49
|
+
ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
|
50
|
+
[@reflection.name, ivars]
|
51
|
+
end
|
52
|
+
|
53
|
+
def marshal_load(data)
|
54
|
+
reflection_name, ivars = data
|
55
|
+
ivars.each { |name, val| instance_variable_set(name, val) }
|
56
|
+
@reflection = @owner.class._reflect_on_association(reflection_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize_attributes(record, attributes = nil) #:nodoc:
|
60
|
+
record.assign_attributes(attributes)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
|
66
|
+
# the kind of the class of the associated objects. Meant to be used as
|
67
|
+
# a sanity check when you are about to assign an associated record.
|
68
|
+
def raise_on_type_mismatch!(record)
|
69
|
+
unless record.is_a?(reflection.klass)
|
70
|
+
fresh_class = reflection.class_name.safe_constantize
|
71
|
+
unless fresh_class && record.is_a?(fresh_class)
|
72
|
+
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
|
73
|
+
"got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
|
74
|
+
raise ActiveRecord::AssociationTypeMismatch, message
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_record(attributes)
|
80
|
+
reflection.build_association(attributes) do |record|
|
81
|
+
initialize_attributes(record, attributes)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# This is the parent Association class which defines the variables
|
2
|
+
# used by all associations.
|
3
|
+
#
|
4
|
+
# The hierarchy is defined as follows:
|
5
|
+
# Association
|
6
|
+
# - SingularAssociation
|
7
|
+
# - BelongsToAssociation
|
8
|
+
# - HasOneAssociation
|
9
|
+
# - CollectionAssociation
|
10
|
+
# - HasManyAssociation
|
11
|
+
|
12
|
+
module DuckRecord::Associations::Builder # :nodoc:
|
13
|
+
class Association #:nodoc:
|
14
|
+
class << self
|
15
|
+
attr_accessor :extensions
|
16
|
+
end
|
17
|
+
self.extensions = []
|
18
|
+
|
19
|
+
VALID_OPTIONS = [:class_name, :anonymous_class, :validate] # :nodoc:
|
20
|
+
|
21
|
+
def self.build(model, name, options, &block)
|
22
|
+
if model.dangerous_attribute_method?(name)
|
23
|
+
raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
|
24
|
+
"this will conflict with a method #{name} already defined by Active Record. " \
|
25
|
+
"Please choose a different association name."
|
26
|
+
end
|
27
|
+
|
28
|
+
extension = define_extensions model, name, &block
|
29
|
+
reflection = create_reflection model, name, options, extension
|
30
|
+
define_accessors model, reflection
|
31
|
+
define_callbacks model, reflection
|
32
|
+
define_validations model, reflection
|
33
|
+
reflection
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.create_reflection(model, name, options, extension = nil)
|
37
|
+
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
|
38
|
+
|
39
|
+
validate_options(options)
|
40
|
+
|
41
|
+
DuckRecord::Reflection.create(macro, name, options, model)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.macro
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.valid_options(options)
|
49
|
+
VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.validate_options(options)
|
53
|
+
options.assert_valid_keys(valid_options(options))
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.define_extensions(model, name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.define_callbacks(model, reflection)
|
60
|
+
Association.extensions.each do |extension|
|
61
|
+
extension.build model, reflection
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Defines the setter and getter methods for the association
|
66
|
+
# class Post < ActiveRecord::Base
|
67
|
+
# has_many :comments
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# Post.first.comments and Post.first.comments= methods are defined by this method...
|
71
|
+
def self.define_accessors(model, reflection)
|
72
|
+
mixin = model.generated_association_methods
|
73
|
+
name = reflection.name
|
74
|
+
define_readers(mixin, name)
|
75
|
+
define_writers(mixin, name)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.define_readers(mixin, name)
|
79
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
80
|
+
def #{name}(*args)
|
81
|
+
association(:#{name}).reader(*args)
|
82
|
+
end
|
83
|
+
CODE
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.define_writers(mixin, name)
|
87
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
88
|
+
def #{name}=(value)
|
89
|
+
association(:#{name}).writer(value)
|
90
|
+
end
|
91
|
+
CODE
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.define_validations(model, reflection)
|
95
|
+
# noop
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
|
2
|
+
|
3
|
+
require "duck_record/associations"
|
4
|
+
|
5
|
+
module DuckRecord::Associations::Builder # :nodoc:
|
6
|
+
class CollectionAssociation < Association #:nodoc:
|
7
|
+
CALLBACKS = [:before_add, :after_add]
|
8
|
+
|
9
|
+
def self.valid_options(options)
|
10
|
+
super + [:before_add, :after_add]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.define_callbacks(model, reflection)
|
14
|
+
super
|
15
|
+
name = reflection.name
|
16
|
+
options = reflection.options
|
17
|
+
CALLBACKS.each { |callback_name|
|
18
|
+
define_callback(model, callback_name, name, options)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.define_extensions(model, name)
|
23
|
+
if block_given?
|
24
|
+
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
|
25
|
+
extension = Module.new(&Proc.new)
|
26
|
+
model.parent.const_set(extension_module_name, extension)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.define_callback(model, callback_name, name, options)
|
31
|
+
full_callback_name = "#{callback_name}_for_#{name}"
|
32
|
+
|
33
|
+
# TODO : why do i need method_defined? I think its because of the inheritance chain
|
34
|
+
model.class_attribute full_callback_name unless model.method_defined?(full_callback_name)
|
35
|
+
callbacks = Array(options[callback_name.to_sym]).map do |callback|
|
36
|
+
case callback
|
37
|
+
when Symbol
|
38
|
+
->(_method, owner, record) { owner.send(callback, record) }
|
39
|
+
when Proc
|
40
|
+
->(_method, owner, record) { callback.call(owner, record) }
|
41
|
+
else
|
42
|
+
->(method, owner, record) { callback.send(method, owner, record) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
model.send "#{full_callback_name}=", callbacks
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module DuckRecord::Associations::Builder # :nodoc:
|
2
|
+
class HasOne < SingularAssociation #:nodoc:
|
3
|
+
def self.macro
|
4
|
+
:has_one
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.define_validations(model, reflection)
|
8
|
+
super
|
9
|
+
|
10
|
+
if reflection.options[:required]
|
11
|
+
model.validates_presence_of reflection.name, message: :required
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# This class is inherited by the has_one and belongs_to association classes
|
2
|
+
|
3
|
+
module DuckRecord::Associations::Builder # :nodoc:
|
4
|
+
class SingularAssociation < Association #:nodoc:
|
5
|
+
def self.define_accessors(model, reflection)
|
6
|
+
super
|
7
|
+
mixin = model.generated_association_methods
|
8
|
+
name = reflection.name
|
9
|
+
|
10
|
+
define_constructors(mixin, name) if reflection.constructable?
|
11
|
+
end
|
12
|
+
|
13
|
+
# Defines the (build|create)_association methods for belongs_to or has_one association
|
14
|
+
def self.define_constructors(mixin, name)
|
15
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
16
|
+
def build_#{name}(*args, &block)
|
17
|
+
association(:#{name}).build(*args, &block)
|
18
|
+
end
|
19
|
+
CODE
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module DuckRecord
|
2
|
+
module Associations
|
3
|
+
# = Active Record Association Collection
|
4
|
+
#
|
5
|
+
# CollectionAssociation is an abstract class that provides common stuff to
|
6
|
+
# ease the implementation of association proxies that represent
|
7
|
+
# collections. See the class hierarchy in Association.
|
8
|
+
#
|
9
|
+
# CollectionAssociation:
|
10
|
+
# HasManyAssociation => has_many
|
11
|
+
# HasManyThroughAssociation + ThroughAssociation => has_many :through
|
12
|
+
#
|
13
|
+
# The CollectionAssociation class provides common methods to the collections
|
14
|
+
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
|
15
|
+
# the +:through association+ option.
|
16
|
+
#
|
17
|
+
# You need to be careful with assumptions regarding the target: The proxy
|
18
|
+
# does not fetch records from the database until it needs them, but new
|
19
|
+
# ones created with +build+ are added to the target. So, the target may be
|
20
|
+
# non-empty and still lack children waiting to be read from the database.
|
21
|
+
# If you look directly to the database you cannot assume that's the entire
|
22
|
+
# collection because new records may have been added to the target, etc.
|
23
|
+
#
|
24
|
+
# If you need to work on all current children, new and existing records,
|
25
|
+
# +load_target+ and the +loaded+ flag are your friends.
|
26
|
+
class CollectionAssociation < Association #:nodoc:
|
27
|
+
# Implements the reader method, e.g. foo.items for Foo.has_many :items
|
28
|
+
def reader
|
29
|
+
@_reader ||= CollectionProxy.new(klass, self)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
|
33
|
+
def writer(records)
|
34
|
+
replace(records)
|
35
|
+
end
|
36
|
+
|
37
|
+
def reset
|
38
|
+
super
|
39
|
+
@target = []
|
40
|
+
end
|
41
|
+
|
42
|
+
def build(attributes = {}, &block)
|
43
|
+
if attributes.is_a?(Array)
|
44
|
+
attributes.collect { |attr| build(attr, &block) }
|
45
|
+
else
|
46
|
+
add_to_target(build_record(attributes)) do |record|
|
47
|
+
yield(record) if block_given?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add +records+ to this association. Returns +self+ so method calls may
|
53
|
+
# be chained. Since << flattens its argument list and inserts each record,
|
54
|
+
# +push+ and +concat+ behave identically.
|
55
|
+
def concat(*records)
|
56
|
+
records = records.flatten
|
57
|
+
@target.concat records
|
58
|
+
end
|
59
|
+
|
60
|
+
# Removes all records from the association without calling callbacks
|
61
|
+
# on the associated records. It honors the +:dependent+ option. However
|
62
|
+
# if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
|
63
|
+
# deletion strategy for the association is applied.
|
64
|
+
#
|
65
|
+
# You can force a particular deletion strategy by passing a parameter.
|
66
|
+
#
|
67
|
+
# Example:
|
68
|
+
#
|
69
|
+
# @author.books.delete_all(:nullify)
|
70
|
+
# @author.books.delete_all(:delete_all)
|
71
|
+
#
|
72
|
+
# See delete for more info.
|
73
|
+
def delete_all
|
74
|
+
@target.clear
|
75
|
+
end
|
76
|
+
|
77
|
+
# Removes +records+ from this association calling +before_remove+ and
|
78
|
+
# +after_remove+ callbacks.
|
79
|
+
#
|
80
|
+
# This method is abstract in the sense that +delete_records+ has to be
|
81
|
+
# provided by descendants. Note this method does not imply the records
|
82
|
+
# are actually removed from the database, that depends precisely on
|
83
|
+
# +delete_records+. They are in any case removed from the collection.
|
84
|
+
def delete(*records)
|
85
|
+
return if records.empty?
|
86
|
+
@target = @target - records
|
87
|
+
end
|
88
|
+
|
89
|
+
# Deletes the +records+ and removes them from this association calling
|
90
|
+
# +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
|
91
|
+
#
|
92
|
+
# Note that this method removes records from the database ignoring the
|
93
|
+
# +:dependent+ option.
|
94
|
+
def destroy(*records)
|
95
|
+
return if records.empty?
|
96
|
+
records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
|
97
|
+
delete_or_destroy(records, :destroy)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the size of the collection by executing a SELECT COUNT(*)
|
101
|
+
# query if the collection hasn't been loaded, and calling
|
102
|
+
# <tt>collection.size</tt> if it has.
|
103
|
+
#
|
104
|
+
# If the collection has been already loaded +size+ and +length+ are
|
105
|
+
# equivalent. If not and you are going to need the records anyway
|
106
|
+
# +length+ will take one less query. Otherwise +size+ is more efficient.
|
107
|
+
#
|
108
|
+
# This method is abstract in the sense that it relies on
|
109
|
+
# +count_records+, which is a method descendants have to provide.
|
110
|
+
def size
|
111
|
+
@target.size
|
112
|
+
end
|
113
|
+
|
114
|
+
def uniq
|
115
|
+
@target.uniq!
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns true if the collection is empty.
|
119
|
+
#
|
120
|
+
# If the collection has been loaded
|
121
|
+
# it is equivalent to <tt>collection.size.zero?</tt>. If the
|
122
|
+
# collection has not been loaded, it is equivalent to
|
123
|
+
# <tt>collection.exists?</tt>. If the collection has not already been
|
124
|
+
# loaded and you are going to fetch the records anyway it is better to
|
125
|
+
# check <tt>collection.length.zero?</tt>.
|
126
|
+
def empty?
|
127
|
+
@target.blank?
|
128
|
+
end
|
129
|
+
|
130
|
+
# Replace this collection with +other_array+. This will perform a diff
|
131
|
+
# and delete/add only records that have changed.
|
132
|
+
def replace(other_array)
|
133
|
+
@target = other_array
|
134
|
+
end
|
135
|
+
|
136
|
+
def include?(record)
|
137
|
+
@target.include?(record)
|
138
|
+
end
|
139
|
+
|
140
|
+
def add_to_target(record, skip_callbacks = false, &block)
|
141
|
+
index = @target.index(record)
|
142
|
+
|
143
|
+
replace_on_target(record, index, skip_callbacks, &block)
|
144
|
+
end
|
145
|
+
|
146
|
+
def replace_on_target(record, index, skip_callbacks)
|
147
|
+
callback(:before_add, record) unless skip_callbacks
|
148
|
+
|
149
|
+
begin
|
150
|
+
if index
|
151
|
+
record_was = target[index]
|
152
|
+
target[index] = record
|
153
|
+
else
|
154
|
+
target << record
|
155
|
+
end
|
156
|
+
|
157
|
+
yield(record) if block_given?
|
158
|
+
rescue
|
159
|
+
if index
|
160
|
+
target[index] = record_was
|
161
|
+
else
|
162
|
+
target.delete(record)
|
163
|
+
end
|
164
|
+
|
165
|
+
raise
|
166
|
+
end
|
167
|
+
|
168
|
+
callback(:after_add, record) unless skip_callbacks
|
169
|
+
|
170
|
+
record
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
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
|