default_delegate.rb
4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
module DefaultDelegate
module ClassMethods
def default_delegate_setting field, options, &block
extend ActsAsHavingSettings::ClassMethods
prefix = options[:prefix] || :default
default_setting = "#{prefix}_#{field}"
settings_items default_setting, default: options[:default], type: :boolean
options[:default_setting] = default_setting
default_delegate field, options
end
# TODO: add some documentation about the methods being added
def default_delegate field, options = {}
# rake db:migrate run?
return unless self.table_exists?
# Rails doesn't define getters for attributes
define_method field do
self[field]
end if field.to_s.in? self.column_names and not self.method_defined? field
define_method "#{field}=" do |value|
self[field] = value
end if field.to_s.in? self.column_names and not self.method_defined? "#{field}="
original_field_method = "original_own_#{field}".freeze
alias_method original_field_method, field
own_field = "own_#{field}".freeze
define_method own_field do
# we prefer the value from dabatase here, and the getter may give a default value
# e.g. Product#name defaults to Product#product_category.name
if field.to_s.in? self.class.column_names then self[field] else self.send original_field_method end
end
alias_method "#{own_field}=", "#{field}="
delegated_field = "delegated_#{field}".freeze
to = options[:to].freeze
define_method delegated_field do
case to
when Symbol
object = self.send to
object.send field if object and object.respond_to? field
when Proc then instance_exec &to
end
end
alias_method "original_#{field}", delegated_field
own_field_blank = "own_#{field}_blank?".freeze
define_method own_field_blank do
own = self.send own_field
# blank? also covers false, use nil? and empty? instead
own.nil? or (own.respond_to? :empty? and own.empty?)
end
own_field_present = "own_#{field}_present?".freeze
define_method own_field_present do
not self.send own_field_blank
end
default_if = options[:default_if].freeze
own_field_is_default = "own_#{field}_default?".freeze
define_method own_field_is_default do
default = self.send own_field_blank
default ||= case default_if
when Proc then instance_exec &default_if
when :equal?
self.send(own_field).equal? self.send(delegated_field)
when Symbol then self.send default_if
else false
end
end
default_setting = options[:default_setting] || "#{options[:prefix] || :default}_#{field}"
# as a field may use other field's default_setting, check for definition
default_setting_with_presence = "#{default_setting}_with_presence".freeze
unless self.method_defined? default_setting_with_presence
define_method default_setting_with_presence do
original_setting = self.send "#{default_setting}_without_presence"
# if the setting is false, see if it should be true; if it is true, respect it.
original_setting = self.send own_field_is_default unless original_setting
original_setting
end
define_method "#{default_setting_with_presence}=" do |value|
# this ensures latter the getter won't get a different
self.send "#{own_field}=", nil if value
self.send "#{default_setting}_without_presence=", value
end
alias_method_chain default_setting, :presence
alias_method_chain "#{default_setting}=", :presence
end
define_method "#{field}_with_default" do
if self.send default_setting
# delegated_field may return nil, so use own instead
# this is the case with some associations (e.g. Product#product_qualifiers)
# FIXME: this shouldn't be necessary, it seems to happens only in certain cases
# (product creation, product global search, etc)
self.send(delegated_field) || self.send(own_field)
else self.send(own_field)
end
end
define_method "#{field}_with_default=" do |*args|
own = self.send "#{own_field}=", *args
# break/set the default setting automatically, used for interfaces
# that don't have the default setting (e.g. manage_products)
self.send "#{default_setting}=", self.send(own_field_is_default)
own
end
alias_method_chain field, :default
alias_method_chain "#{field}=", :default
include DefaultDelegate::InstanceMethods
end
end
module InstanceMethods
end
end
ActiveRecord::Base.extend DefaultDelegate::ClassMethods