moribus 0.0.1
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 +15 -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 +19 -0
- data/LICENSE +20 -0
- data/README.md +104 -0
- data/Rakefile +15 -0
- data/lib/colorized_text.rb +33 -0
- data/lib/moribus.rb +133 -0
- data/lib/moribus/aggregated_behavior.rb +80 -0
- data/lib/moribus/aggregated_cache_behavior.rb +76 -0
- data/lib/moribus/alias_association.rb +106 -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 +120 -0
- data/lib/moribus/tracked_behavior.rb +91 -0
- data/lib/moribus/version.rb +3 -0
- data/moribus.gemspec +33 -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 +61 -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 +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -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 +209 -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,106 @@
|
|
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, opts = {})
|
34
|
+
alias_name = opts.delete(:alias)
|
35
|
+
reflection = super(name, opts)
|
36
|
+
alias_association(alias_name, name) if alias_name
|
37
|
+
reflection
|
38
|
+
end
|
39
|
+
|
40
|
+
# Allows :alias option to alias has_many association
|
41
|
+
def has_many(name, opts = {})
|
42
|
+
alias_name = opts.delete(:alias)
|
43
|
+
reflection = super(name, opts)
|
44
|
+
alias_association(alias_name, name) if alias_name
|
45
|
+
reflection
|
46
|
+
end
|
47
|
+
|
48
|
+
# Allows :alias option to alias has_one association
|
49
|
+
def has_one(name, opts = {})
|
50
|
+
alias_name = opts.delete(:alias)
|
51
|
+
reflection = super(name, opts)
|
52
|
+
alias_association(alias_name, name) if alias_name
|
53
|
+
reflection
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# Aliases association methods for a given association reflections: creates
|
58
|
+
# association accessors aliases (such as <tt>other</tt> and <tt>other=</tt>),
|
59
|
+
# also aliases association-specific methods(such as <tt>build_other</tt> and
|
60
|
+
# <tt>create_other</tt> for singular association, and <tt>other_ids</tt> and
|
61
|
+
# <tt>other_ids=</tt> for collection association)
|
62
|
+
def alias_association_methods(alias_name, reflection)
|
63
|
+
association_name = reflection.name
|
64
|
+
alias_association_accessor_methods(alias_name, association_name)
|
65
|
+
case reflection.macro
|
66
|
+
when :has_one, :belongs_to
|
67
|
+
alias_singular_association_methods(alias_name, association_name)
|
68
|
+
when :has_many, :has_and_belongs_to_many
|
69
|
+
alias_collection_association_methods(alias_name, association_name)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
private :alias_association_methods
|
73
|
+
|
74
|
+
# Aliases association accessor methods:
|
75
|
+
# * <tt>other</tt>
|
76
|
+
# * <tt>other=</tt>
|
77
|
+
def alias_association_accessor_methods(alias_name, association_name)
|
78
|
+
alias_method alias_name, association_name
|
79
|
+
alias_method "#{alias_name}=", "#{association_name}="
|
80
|
+
end
|
81
|
+
private :alias_association_accessor_methods
|
82
|
+
|
83
|
+
# Aliases singular association methods:
|
84
|
+
# * <tt>build_other</tt>
|
85
|
+
# * <tt>create_other</tt>
|
86
|
+
# * <tt>create_other!</tt>
|
87
|
+
def alias_singular_association_methods(alias_name, association_name)
|
88
|
+
alias_method "build_#{alias_name}", "build_#{association_name}"
|
89
|
+
alias_method "create_#{alias_name}", "create_#{association_name}"
|
90
|
+
alias_method "create_#{alias_name}!", "create_#{association_name}!"
|
91
|
+
end
|
92
|
+
private :alias_singular_association_methods
|
93
|
+
|
94
|
+
# Aliases collection association methods:
|
95
|
+
# * <tt>other_ids</tt>
|
96
|
+
# * <tt>other_ids=</tt>
|
97
|
+
def alias_collection_association_methods(alias_name, association_name)
|
98
|
+
singularized_alias_name = alias_name.to_s.singularize
|
99
|
+
singularized_association_name = association_name.to_s.singularize
|
100
|
+
alias_method "#{singularized_alias_name}_ids", "#{singularized_association_name}_ids"
|
101
|
+
alias_method "#{singularized_alias_name}_ids=", "#{singularized_association_name}_ids="
|
102
|
+
end
|
103
|
+
private :alias_collection_association_methods
|
104
|
+
end
|
105
|
+
end
|
106
|
+
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 unless model.attribute_methods_generated?
|
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 unless klass.attribute_methods_generated?
|
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
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Moribus
|
2
|
+
module Extensions
|
3
|
+
# Minor extension for Rails' +has_one+ association that will help
|
4
|
+
# dealing with current record assignment.
|
5
|
+
module HasCurrentExtension
|
6
|
+
# Sets 'is_current' flag of overridden record to false, instead
|
7
|
+
# of deleting it or setting foreign key to nil.
|
8
|
+
def remove_target!(*)
|
9
|
+
if target.new_record?
|
10
|
+
target.is_current = false
|
11
|
+
else
|
12
|
+
target.update_attribute(:is_current, false)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|