HornsAndHooves-moribus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/.rspec +4 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.simplecov +42 -0
- data/.travis.yml +8 -0
- data/Gemfile +17 -0
- data/HornsAndHooves-moribus.gemspec +31 -0
- data/LICENSE +21 -0
- data/README.md +110 -0
- data/Rakefile +15 -0
- data/lib/colorized_text.rb +33 -0
- data/lib/moribus.rb +138 -0
- data/lib/moribus/aggregated_behavior.rb +80 -0
- data/lib/moribus/aggregated_cache_behavior.rb +76 -0
- data/lib/moribus/alias_association.rb +111 -0
- data/lib/moribus/extensions.rb +37 -0
- data/lib/moribus/extensions/delegate_associated.rb +48 -0
- data/lib/moribus/extensions/has_aggregated_extension.rb +94 -0
- data/lib/moribus/extensions/has_current_extension.rb +17 -0
- data/lib/moribus/macros.rb +135 -0
- data/lib/moribus/tracked_behavior.rb +91 -0
- data/lib/moribus/version.rb +3 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +53 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +31 -0
- data/spec/dummy/config/environments/production.rb +70 -0
- data/spec/dummy/config/environments/test.rb +34 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/moribus/alias_association_spec.rb +88 -0
- data/spec/moribus/macros_spec.rb +7 -0
- data/spec/moribus_spec.rb +332 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/moribus_spec_model.rb +57 -0
- metadata +247 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module Moribus
|
2
|
+
# Adds aggregated behavior to a model. An aggregated model tries to insure
|
3
|
+
# it will not duplicate itself for whatever parents it belongs to. Whenever
|
4
|
+
# an aggregated model is about to be saved, it uses its attributes to
|
5
|
+
# perform a lookup for an existing record with the same attributes. If the
|
6
|
+
# lookup succeeds, its id is used to replace id of model being saved, and
|
7
|
+
# no 'INSERT' statement is executed. If the lookup fails, the original AR
|
8
|
+
# save routines are performed.
|
9
|
+
#
|
10
|
+
# This behavior ignores by default the columns it doesn't consider to
|
11
|
+
# contain content such as the ones created and used by ActiveRecord. These
|
12
|
+
# can be expanded through the API:
|
13
|
+
# @example:
|
14
|
+
# acts_as_aggregated :non_content_columns => %w(some other colums)
|
15
|
+
module AggregatedBehavior
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
|
18
|
+
included do
|
19
|
+
# specifies a list of attributes to exclude from lookup
|
20
|
+
class_attribute :aggregated_behaviour_non_content_columns, :instance_writer => false
|
21
|
+
self.aggregated_behaviour_non_content_columns = %w(id created_at updated_at lock_version)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Override the original AR::Base #save method with the aggregated
|
25
|
+
# behavior. This cannot be done using a before_save callback, because, if
|
26
|
+
# the lookup succeeds, we don't want the original #save to be executed.
|
27
|
+
# But if +false+ is returned by the callback, it will also be returned
|
28
|
+
# by the #save method, wrongly indicating the result of saving.
|
29
|
+
def save(*)
|
30
|
+
@updated_as_aggregated = false
|
31
|
+
run_callbacks(:save) do
|
32
|
+
return (lookup_self_and_replace or super) if new_record?
|
33
|
+
|
34
|
+
is_any_content_attr_changed =
|
35
|
+
attributes.except(*aggregated_behaviour_non_content_columns).keys.
|
36
|
+
any?{ |attr| attribute_changed?(attr) }
|
37
|
+
|
38
|
+
if is_any_content_attr_changed
|
39
|
+
to_new_record!
|
40
|
+
lookup_self_and_replace or return super
|
41
|
+
|
42
|
+
true
|
43
|
+
else
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Bang version of #save.
|
50
|
+
def save!(*args)
|
51
|
+
save(*args) or raise ActiveRecord::RecordNotSaved
|
52
|
+
end
|
53
|
+
|
54
|
+
# Use the +lookup_relation+ to get the very first existing record that
|
55
|
+
# corresponds to +self+.
|
56
|
+
def lookup_self
|
57
|
+
lookup_relation.first
|
58
|
+
end
|
59
|
+
private :lookup_self
|
60
|
+
|
61
|
+
# Use the attributes of +self+ to generate a relation that corresponds to
|
62
|
+
# the existing record in the table with the same attributes.
|
63
|
+
def lookup_relation
|
64
|
+
self.class.unscoped.where(attributes.except(*aggregated_behaviour_non_content_columns))
|
65
|
+
end
|
66
|
+
private :lookup_relation
|
67
|
+
|
68
|
+
# If #lookup_self successfully returns a record, 'replace' +self+ by it
|
69
|
+
# (using its id, created_at, updated_at values).
|
70
|
+
def lookup_self_and_replace
|
71
|
+
@updated_as_aggregated = true
|
72
|
+
existing = lookup_self
|
73
|
+
|
74
|
+
if existing.present? then
|
75
|
+
to_persistent!(existing)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
private :lookup_self_and_replace
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Moribus
|
2
|
+
# This module provides additional in-memory caching for model that
|
3
|
+
# behaves in aggregated way. For that reason, <tt>:aggregated_records_cache</tt>
|
4
|
+
# hash class instance variable is added, and the <tt>@aggregated_caching_column</tt>
|
5
|
+
# class instance variable should be defined by class. The value of
|
6
|
+
# corresponding attribute of the model is used as a cache key.
|
7
|
+
#
|
8
|
+
# The original +lookup_self+ method is overloaded to lookup in cache
|
9
|
+
# first. If this lookup fails, native aggregated routines are performed
|
10
|
+
# and resulting record is added to the cache.
|
11
|
+
#
|
12
|
+
# Please note that this module is not to be included manually. Use
|
13
|
+
# class-level +acts_as_aggregated+ instead, supplied with an <tt>:cache_by</tt>
|
14
|
+
# option:
|
15
|
+
#
|
16
|
+
# class EmailDomain < ActiveRecord::Base
|
17
|
+
# acts_as_aggregated :cache_by => :domain
|
18
|
+
# # .. rest of definition
|
19
|
+
# end
|
20
|
+
module AggregatedCacheBehavior
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
# Raised when trying to include the module to a non-aggregated model.
|
24
|
+
NotAggregatedError = Class.new(::ArgumentError)
|
25
|
+
|
26
|
+
included do
|
27
|
+
unless self < AggregatedBehavior
|
28
|
+
raise NotAggregatedError, 'AggregatedCache can be used only in Aggregated models'
|
29
|
+
end
|
30
|
+
|
31
|
+
class_attribute :aggregated_records_cache
|
32
|
+
self.aggregated_records_cache = {}
|
33
|
+
|
34
|
+
after_save :cache_aggregated_record, :on => :create
|
35
|
+
end
|
36
|
+
|
37
|
+
# Class methods for model that includes AggregatedCacheBehavior
|
38
|
+
module ClassMethods
|
39
|
+
# Empty the cache of aggregated records.
|
40
|
+
def clear_cache
|
41
|
+
self.aggregated_records_cache = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return the column (attribute). Its value is used as a key for
|
45
|
+
# caching records.
|
46
|
+
def aggregated_caching_column
|
47
|
+
@aggregated_caching_column
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Overridden for caching support.
|
52
|
+
def lookup_self
|
53
|
+
cache = self.class.aggregated_records_cache
|
54
|
+
cache_by = caching_attribute
|
55
|
+
return cache[cache_by] if cache.key? cache_by
|
56
|
+
lookup_result = super
|
57
|
+
cache[cache_by] = lookup_result if lookup_result
|
58
|
+
lookup_result
|
59
|
+
end
|
60
|
+
private :lookup_self
|
61
|
+
|
62
|
+
# Cache the record.
|
63
|
+
def cache_aggregated_record
|
64
|
+
cache_by = caching_attribute
|
65
|
+
self.class.aggregated_records_cache[cache_by] = dup.tap{ |d| d.to_persistent!(self); d.freeze }
|
66
|
+
end
|
67
|
+
private :cache_aggregated_record
|
68
|
+
|
69
|
+
# Return the value of the caching column (attribute) used as a key of
|
70
|
+
# the records cache.
|
71
|
+
def caching_attribute
|
72
|
+
read_attribute(self.class.aggregated_caching_column)
|
73
|
+
end
|
74
|
+
private :caching_attribute
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Moribus
|
2
|
+
# When included, adds alias_association public class method for aliasing
|
3
|
+
# specific association. Association name and association-specific methods
|
4
|
+
# will be aliased. Example:
|
5
|
+
#
|
6
|
+
# class Customer
|
7
|
+
# belongs_to :person_name
|
8
|
+
# has_many :orders
|
9
|
+
# alias_association :name, :person_name
|
10
|
+
# alias_association :bookings, :orders
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Customer.reflect_on_association(:name) # => #<ActiveRecord::Reflection::AssociationReflection ...>
|
14
|
+
# c = Customer.includes(:bookings).first
|
15
|
+
# c.booking_ids # => [1, 2, 3]
|
16
|
+
# c.build_name(:first_name => 'John', :last_name => 'Smith') # => #<PersonName ...>
|
17
|
+
module AliasAssociation
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
# Class methods for ActiveRecord::Base
|
21
|
+
module ClassMethods
|
22
|
+
# Aliases association reflection in reflections hash and
|
23
|
+
# association-specific methods. See module description for example
|
24
|
+
def alias_association(alias_name, association_name)
|
25
|
+
if reflection = reflect_on_association(association_name)
|
26
|
+
reflections[alias_name] = reflections[association_name]
|
27
|
+
alias_association_methods(alias_name, reflection)
|
28
|
+
reflection
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Allows :alias option to alias belongs_to association
|
33
|
+
def belongs_to(name, scope = nil, options = {})
|
34
|
+
options = scope if scope.is_a?(Hash)
|
35
|
+
|
36
|
+
alias_name = options.delete(:alias)
|
37
|
+
reflection = super(name, scope, options)
|
38
|
+
alias_association(alias_name, name) if alias_name
|
39
|
+
reflection
|
40
|
+
end
|
41
|
+
|
42
|
+
# Allows :alias option to alias has_many association
|
43
|
+
def has_many(name, scope = nil, options = {}, &extension)
|
44
|
+
options = scope if scope.is_a?(Hash)
|
45
|
+
|
46
|
+
alias_name = options.delete(:alias)
|
47
|
+
reflection = super(name, scope, options, &extension)
|
48
|
+
alias_association(alias_name, name) if alias_name
|
49
|
+
reflection
|
50
|
+
end
|
51
|
+
|
52
|
+
# Allows :alias option to alias has_one association
|
53
|
+
def has_one(name, scope = nil, options = {})
|
54
|
+
options = scope if scope.is_a?(Hash)
|
55
|
+
|
56
|
+
alias_name = options.delete(:alias)
|
57
|
+
reflection = super(name, scope, options)
|
58
|
+
alias_association(alias_name, name) if alias_name
|
59
|
+
reflection
|
60
|
+
end
|
61
|
+
|
62
|
+
# Aliases association methods for a given association reflections: creates
|
63
|
+
# association accessors aliases (such as <tt>other</tt> and <tt>other=</tt>),
|
64
|
+
# also aliases association-specific methods(such as <tt>build_other</tt> and
|
65
|
+
# <tt>create_other</tt> for singular association, and <tt>other_ids</tt> and
|
66
|
+
# <tt>other_ids=</tt> for collection association)
|
67
|
+
def alias_association_methods(alias_name, reflection)
|
68
|
+
association_name = reflection.name
|
69
|
+
alias_association_accessor_methods(alias_name, association_name)
|
70
|
+
case reflection.macro
|
71
|
+
when :has_one, :belongs_to
|
72
|
+
alias_singular_association_methods(alias_name, association_name)
|
73
|
+
when :has_many, :has_and_belongs_to_many
|
74
|
+
alias_collection_association_methods(alias_name, association_name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
private :alias_association_methods
|
78
|
+
|
79
|
+
# Aliases association accessor methods:
|
80
|
+
# * <tt>other</tt>
|
81
|
+
# * <tt>other=</tt>
|
82
|
+
def alias_association_accessor_methods(alias_name, association_name)
|
83
|
+
alias_method alias_name, association_name
|
84
|
+
alias_method "#{alias_name}=", "#{association_name}="
|
85
|
+
end
|
86
|
+
private :alias_association_accessor_methods
|
87
|
+
|
88
|
+
# Aliases singular association methods:
|
89
|
+
# * <tt>build_other</tt>
|
90
|
+
# * <tt>create_other</tt>
|
91
|
+
# * <tt>create_other!</tt>
|
92
|
+
def alias_singular_association_methods(alias_name, association_name)
|
93
|
+
alias_method "build_#{alias_name}", "build_#{association_name}"
|
94
|
+
alias_method "create_#{alias_name}", "create_#{association_name}"
|
95
|
+
alias_method "create_#{alias_name}!", "create_#{association_name}!"
|
96
|
+
end
|
97
|
+
private :alias_singular_association_methods
|
98
|
+
|
99
|
+
# Aliases collection association methods:
|
100
|
+
# * <tt>other_ids</tt>
|
101
|
+
# * <tt>other_ids=</tt>
|
102
|
+
def alias_collection_association_methods(alias_name, association_name)
|
103
|
+
singularized_alias_name = alias_name.to_s.singularize
|
104
|
+
singularized_association_name = association_name.to_s.singularize
|
105
|
+
alias_method "#{singularized_alias_name}_ids", "#{singularized_association_name}_ids"
|
106
|
+
alias_method "#{singularized_alias_name}_ids=", "#{singularized_association_name}_ids="
|
107
|
+
end
|
108
|
+
private :alias_collection_association_methods
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Moribus
|
2
|
+
# Extends the default Rails +has_one+ and +belongs_to+ associations
|
3
|
+
# to enable tracked and aggregated behaviors.
|
4
|
+
module Extensions
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
|
8
|
+
autoload :HasAggregatedExtension
|
9
|
+
autoload :HasCurrentExtension
|
10
|
+
autoload :DelegateAssociated
|
11
|
+
|
12
|
+
# :nodoc:
|
13
|
+
module ClassMethods
|
14
|
+
# Adds special delegation for +has_aggregated+ association to Rails'
|
15
|
+
# +belongs_to+ reflection object.
|
16
|
+
def extend_has_aggregated_reflection(reflection)
|
17
|
+
HasAggregatedExtension::Helper.new(self, reflection).extend
|
18
|
+
end
|
19
|
+
private :extend_has_aggregated_reflection
|
20
|
+
end
|
21
|
+
|
22
|
+
# Overrides Rails' default #association method to extend resulting
|
23
|
+
# +association+ objects by custom behaviors.
|
24
|
+
def association(name)
|
25
|
+
association = super
|
26
|
+
reflection = self.class.reflect_on_association(name)
|
27
|
+
case reflection.macro
|
28
|
+
when :belongs_to
|
29
|
+
association.extend(HasAggregatedExtension) if reflection.options[:aggregated]
|
30
|
+
when :has_one
|
31
|
+
association.extend(HasCurrentExtension) if reflection.options[:is_current]
|
32
|
+
else # do nothing
|
33
|
+
end
|
34
|
+
association
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Moribus
|
2
|
+
module Extensions
|
3
|
+
# This module is included by the class on the first call to
|
4
|
+
# +delegate_associated+ method. When included, it will add
|
5
|
+
# +classes_delegating_to+ class attribute to memorize classes
|
6
|
+
# of delegated associations. This information will be used
|
7
|
+
# for multi-parameter attributes assignment.
|
8
|
+
module DelegateAssociated
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :classes_delegating_to
|
13
|
+
self.classes_delegating_to = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Overloaded AR::Base method that will additionally check column
|
17
|
+
# in delegated associations classes. Purpose:
|
18
|
+
#
|
19
|
+
# class Customer < ActiveRecord::Base
|
20
|
+
# has_one_current :customer_info
|
21
|
+
#
|
22
|
+
# delegate_associated :date_of_birth, :to => :customer_info
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# customer = Customer.new({
|
26
|
+
# 'date_of_birth(1i)' => '1950',
|
27
|
+
# 'date_of_birth(2i)' => '03',
|
28
|
+
# 'date_of_birth(3i)' => '18'
|
29
|
+
# })
|
30
|
+
#
|
31
|
+
# Here, for multi-parameter attribute assignment, Rails will try to
|
32
|
+
# get the column class of 'date_of_birth' attribute. Since it is not
|
33
|
+
# presented in Customer, the code will result in exception without
|
34
|
+
# the following hook:
|
35
|
+
def column_for_attribute(name)
|
36
|
+
unless (column = super).nil?
|
37
|
+
return column
|
38
|
+
end
|
39
|
+
|
40
|
+
self.class.classes_delegating_to.each do |klass|
|
41
|
+
column = klass.columns_hash[name.to_s]
|
42
|
+
return column unless column.nil?
|
43
|
+
end
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Moribus
|
2
|
+
module Extensions
|
3
|
+
# Minor extension for Rails' +belongs_to+ association that will correct
|
4
|
+
# foreign key assignment during association autosave.
|
5
|
+
module HasAggregatedExtension
|
6
|
+
# Return +true+ if the association has an @updated value (set by
|
7
|
+
# default Rails behavior) or if the target record was updated during
|
8
|
+
# lookup, indicating that the association owner's foreign key should
|
9
|
+
# be updated also.
|
10
|
+
def updated?
|
11
|
+
@updated || target.try(:updated_as_aggregated?)
|
12
|
+
end
|
13
|
+
|
14
|
+
# This helper class is used to effectively extend +has_aggregated+
|
15
|
+
# association by adding attribute delegation of attributes and enum
|
16
|
+
# readers to the effective reader of the association owner. Behaves
|
17
|
+
# much like ActiveRecord::Associations::Builder classes.
|
18
|
+
class Helper
|
19
|
+
# Among all attribute methods, we're interested only in reader and
|
20
|
+
# writers - discard the rest
|
21
|
+
EXCLUDE_METHODS_REGEXP = /^_|\?$|^reset|_cast$|_was$|_change!?$|lock_version|created_at|updated_at/
|
22
|
+
|
23
|
+
attr_reader :model, :reflection
|
24
|
+
|
25
|
+
# Save association owner and reflection for subsequent processing.
|
26
|
+
def initialize(model, reflection)
|
27
|
+
@model, @reflection = model, reflection
|
28
|
+
end
|
29
|
+
|
30
|
+
# Extend association: add the delegation module to the reflection,
|
31
|
+
# fill it with the delegation methods and include it into the model.
|
32
|
+
def extend
|
33
|
+
define_delegation_module(reflection)
|
34
|
+
add_delegated_methods(reflection)
|
35
|
+
include_delegation_module(reflection)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Define the delegation module for a reflection, available through
|
39
|
+
# #delegated_attribute_methods* method.
|
40
|
+
def define_delegation_module(reflection)
|
41
|
+
reflection.define_singleton_method(:delegated_attribute_methods) do
|
42
|
+
@delegated_attribute_methods ||= Module.new
|
43
|
+
end
|
44
|
+
end
|
45
|
+
private :define_delegation_module
|
46
|
+
|
47
|
+
# Add all the interesting methods of the association's klass:
|
48
|
+
# generated attribute readers and writers, as well as enum readers
|
49
|
+
# and writers.
|
50
|
+
def add_delegated_methods(reflection)
|
51
|
+
mod = reflection.delegated_attribute_methods
|
52
|
+
model.define_attribute_methods
|
53
|
+
methods_to_delegate = methods_to_delegate_to(reflection) - model.instance_methods.map(&:to_sym)
|
54
|
+
methods_to_delegate.each do |method|
|
55
|
+
mod.delegate method, :to => name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
private :add_delegated_methods
|
59
|
+
|
60
|
+
# Return a list of methods we want to delegate to the association:
|
61
|
+
# will generate attribute methods for a klass if they have not yet
|
62
|
+
# been generated by Rails, and will select reader and writer methods
|
63
|
+
# from the generated model. Also, adds enum readers and writers to
|
64
|
+
# a result. Also, it selects methods that can be treated as
|
65
|
+
# 'custom_writers' - they were declared within the class itself
|
66
|
+
# and have a names like '<column_name>='
|
67
|
+
def methods_to_delegate_to(reflection)
|
68
|
+
klass = reflection.klass
|
69
|
+
enum_methods = klass.reflect_on_all_enumerated.map do |reflection|
|
70
|
+
name = reflection.name
|
71
|
+
[name, "#{name}="]
|
72
|
+
end
|
73
|
+
klass.define_attribute_methods
|
74
|
+
attribute_methods = klass.generated_attribute_methods.instance_methods.select{ |m| m !~ EXCLUDE_METHODS_REGEXP }
|
75
|
+
custom_writers = klass.instance_methods(false).map(&:to_s) & klass.column_names.map{ |name| "#{name}=" }
|
76
|
+
(attribute_methods + enum_methods.flatten + custom_writers).map(&:to_sym)
|
77
|
+
end
|
78
|
+
private :methods_to_delegate_to
|
79
|
+
|
80
|
+
# Include the reflection's attributes delegation module into a model.
|
81
|
+
def include_delegation_module(reflection)
|
82
|
+
model.send(:include, reflection.delegated_attribute_methods)
|
83
|
+
end
|
84
|
+
private :include_delegation_module
|
85
|
+
|
86
|
+
# Return the effective name of the association we're delegating to.
|
87
|
+
def name
|
88
|
+
@reflection_name ||= "effective_#{reflection.name}"
|
89
|
+
end
|
90
|
+
private :name
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|