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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
# File 'lib/hstore_accessor/macro.rb', line 4
def hstore_accessor(hstore_attribute, fields)
@@hstore_keys_and_types ||= {}
"hstore_metadata_for_#{hstore_attribute}".tap do |method_name|
singleton_class.send(:define_method, method_name) do
fields
end
delegate method_name, to: :class
end
field_methods = Module.new
if ActiveRecord::VERSION::STRING.to_f >= 4.2
singleton_class.send(:define_method, :type_for_attribute) do |attribute|
data_type = @@hstore_keys_and_types[attribute]
if data_type
TypeHelpers::TYPES[data_type].new
else
super(attribute)
end
end
singleton_class.send(:define_method, :column_for_attribute) do |attribute|
data_type = @@hstore_keys_and_types[attribute.to_s]
if data_type
TypeHelpers.column_type_for(attribute.to_s, data_type)
else
super(attribute)
end
end
else
field_methods.send(:define_method, :column_for_attribute) do |attribute|
data_type = @@hstore_keys_and_types[attribute.to_s]
if data_type
TypeHelpers.column_type_for(attribute.to_s, data_type)
else
super(attribute)
end
end
end
fields.each do |key, type|
data_type = type
store_key = key
if type.is_a?(Hash)
type = type.with_indifferent_access
data_type = type[:data_type]
store_key = type[:store_key]
end
data_type = data_type.to_sym
raise Serialization::InvalidDataTypeError unless Serialization::VALID_TYPES.include?(data_type)
@@hstore_keys_and_types[key.to_s] = data_type
field_methods.instance_eval do
define_method("#{key}=") do |value|
casted_value = TypeHelpers.cast(data_type, value)
serialized_value = Serialization.serialize(data_type, casted_value)
unless send(key) == casted_value
send("#{hstore_attribute}_will_change!")
end
send("#{hstore_attribute}=", (send(hstore_attribute) || {}).merge(store_key.to_s => serialized_value))
end
define_method(key) do
value = send(hstore_attribute) && send(hstore_attribute).with_indifferent_access[store_key.to_s]
Serialization.deserialize(data_type, value)
end
define_method("#{key}?") do
send(key).present?
end
define_method("#{key}_changed?") do
send("#{key}_change").present?
end
define_method("#{key}_was") do
(send(:attribute_was, hstore_attribute.to_s) || {})[key.to_s]
end
define_method("#{key}_change") do
hstore_changes = send("#{hstore_attribute}_change")
return if hstore_changes.nil?
attribute_changes = hstore_changes.map { |change| change.try(:[], store_key.to_s) }
attribute_changes.uniq.size == 1 ? nil : attribute_changes
end
define_method("restore_#{key}!") do
old_hstore = send("#{hstore_attribute}_change").try(:first) || {}
send("#{key}=", old_hstore[key.to_s])
end
define_method("reset_#{key}!") do
if ActiveRecord::VERSION::STRING.to_f >= 4.2
ActiveSupport::Deprecation.warn(<<-MSG.squish)
`#reset_#{key}!` is deprecated and will be removed on Rails 5.
Please use `#restore_#{key}!` instead.
MSG
end
send("restore_#{key}!")
end
define_method("#{key}_will_change!") do
send("#{hstore_attribute}_will_change!")
end
end
query_field = "#{table_name}.#{hstore_attribute} -> '#{store_key}'"
eq_query_field = "#{table_name}.#{hstore_attribute} @> hstore('#{store_key}', ?)"
case data_type
when :string
send(:scope, "with_#{key}", -> value { where(eq_query_field, value.to_s) })
when :integer
send(:scope, "#{key}_lt", -> value { where("(#{query_field})::#{data_type} < ?", value.to_s) })
send(:scope, "#{key}_lte", -> value { where("(#{query_field})::#{data_type} <= ?", value.to_s) })
send(:scope, "#{key}_eq", -> value { where(eq_query_field, value.to_s) })
send(:scope, "#{key}_gte", -> value { where("(#{query_field})::#{data_type} >= ?", value.to_s) })
send(:scope, "#{key}_gt", -> value { where("(#{query_field})::#{data_type} > ?", value.to_s) })
when :float, :decimal
send(:scope, "#{key}_lt", -> value { where("(#{query_field})::#{data_type} < ?", value.to_s) })
send(:scope, "#{key}_lte", -> value { where("(#{query_field})::#{data_type} <= ?", value.to_s) })
send(:scope, "#{key}_eq", -> value { where("(#{query_field})::#{data_type} = ?", value.to_s) })
send(:scope, "#{key}_gte", -> value { where("(#{query_field})::#{data_type} >= ?", value.to_s) })
send(:scope, "#{key}_gt", -> value { where("(#{query_field})::#{data_type} > ?", value.to_s) })
when :datetime
send(:scope, "#{key}_before", -> value { where("(#{query_field})::integer < ?", value.to_i) })
send(:scope, "#{key}_eq", -> value { where(eq_query_field, value.to_i.to_s) })
send(:scope, "#{key}_after", -> value { where("(#{query_field})::integer > ?", value.to_i) })
when :date
send(:scope, "#{key}_before", -> value { where("#{query_field} < ?", value.to_s) })
send(:scope, "#{key}_eq", -> value { where(eq_query_field, value.to_s) })
send(:scope, "#{key}_after", -> value { where("#{query_field} > ?", value.to_s) })
when :boolean
send(:scope, "is_#{key}", -> { where(eq_query_field, "true") })
send(:scope, "not_#{key}", -> { where(eq_query_field, "false") })
end
end
include field_methods
end
|