eav_hashes 1.0.1 → 1.0.2
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 +7 -0
- data/MIT-LICENSE +20 -20
- data/README.md +147 -147
- data/Rakefile +30 -30
- data/lib/eav_hashes/activerecord_extension.rb +37 -37
- data/lib/eav_hashes/eav_entry.rb +128 -128
- data/lib/eav_hashes/eav_hash.rb +167 -167
- data/lib/eav_hashes/util.rb +122 -122
- data/lib/eav_hashes/version.rb +5 -5
- data/lib/eav_hashes.rb +8 -8
- data/lib/generators/eav_migration/USAGE +26 -26
- data/lib/generators/eav_migration/eav_migration.rb +35 -35
- data/lib/generators/eav_migration/templates/eav_migration.erb +15 -15
- data/spec/dummy/README.rdoc +261 -261
- data/spec/dummy/Rakefile +7 -7
- data/spec/dummy/app/assets/javascripts/application.js +15 -15
- data/spec/dummy/app/assets/stylesheets/application.css +13 -13
- data/spec/dummy/app/controllers/application_controller.rb +3 -3
- data/spec/dummy/app/helpers/application_helper.rb +2 -2
- data/spec/dummy/app/models/custom_test_object.rb +6 -6
- data/spec/dummy/app/models/product.rb +3 -4
- data/spec/dummy/app/views/layouts/application.html.erb +14 -14
- data/spec/dummy/config/application.rb +62 -68
- data/spec/dummy/config/boot.rb +9 -9
- data/spec/dummy/config/database.yml +25 -25
- data/spec/dummy/config/environment.rb +5 -5
- data/spec/dummy/config/environments/development.rb +34 -37
- data/spec/dummy/config/environments/production.rb +70 -67
- data/spec/dummy/config/environments/test.rb +34 -37
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -7
- data/spec/dummy/config/initializers/inflections.rb +15 -15
- data/spec/dummy/config/initializers/mime_types.rb +5 -5
- data/spec/dummy/config/initializers/secret_token.rb +7 -7
- data/spec/dummy/config/initializers/session_store.rb +8 -8
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -14
- data/spec/dummy/config/locales/en.yml +5 -5
- data/spec/dummy/config/routes.rb +58 -58
- data/spec/dummy/config.ru +4 -4
- data/spec/dummy/db/migrate/20121206133059_create_products.rb +9 -9
- data/spec/dummy/db/migrate/20121210055854_create_product_tech_specs.rb +15 -15
- data/spec/dummy/db/seeds.rb +30 -30
- data/spec/dummy/public/404.html +26 -26
- data/spec/dummy/public/422.html +26 -26
- data/spec/dummy/public/500.html +25 -25
- data/spec/dummy/script/rails +6 -6
- data/spec/lib/eav_hashes/eav_hash_spec.rb +147 -137
- data/spec/lib/generators/eav_migration_spec.rb +60 -60
- data/spec/spec_helper.rb +24 -24
- metadata +25 -44
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +0 -35
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/ENV=test.log +0 -1
- data/spec/dummy/log/development.log +0 -46
- data/spec/dummy/log/test.log +0 -4623
data/lib/eav_hashes/eav_entry.rb
CHANGED
@@ -1,129 +1,129 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module EavHashes
|
3
|
-
# Used instead of nil when a nil value is assigned
|
4
|
-
# (otherwise, the value will try to deserialize itself and
|
5
|
-
# that would break everything horrifically)
|
6
|
-
class NilPlaceholder; end
|
7
|
-
|
8
|
-
# Represent an EAV row. This class should NOT be used directly, instead it should be inherited from
|
9
|
-
# by the class generated by eav_hash_for.
|
10
|
-
class EavEntry < ActiveRecord::Base
|
11
|
-
# prevent activerecord from thinking we're trying to do STI
|
12
|
-
self.abstract_class = true
|
13
|
-
|
14
|
-
# Tell ActiveRecord to convert the value to its DB storable format
|
15
|
-
before_save :serialize_value
|
16
|
-
|
17
|
-
# Let the key be assignable only once on creation
|
18
|
-
attr_readonly :entry_key
|
19
|
-
|
20
|
-
# Contains the values the value_type column should have based on the type of the value being stored
|
21
|
-
SUPPORTED_TYPES = {
|
22
|
-
:String => 0,
|
23
|
-
:Symbol => 1,
|
24
|
-
:Integer => 2,
|
25
|
-
:Fixnum => 2,
|
26
|
-
:Bignum => 2,
|
27
|
-
:Float => 3,
|
28
|
-
:Complex => 4,
|
29
|
-
:Rational => 5,
|
30
|
-
:Boolean => 6, # For code readability
|
31
|
-
:TrueClass => 6,
|
32
|
-
:FalseClass => 6,
|
33
|
-
:Object => 7 # anything else (including Hashes, Arrays) will be serialized to yaml and saved as Object
|
34
|
-
}
|
35
|
-
|
36
|
-
# Does some sanity checks.
|
37
|
-
def after_initialize
|
38
|
-
raise "key should be a string or symbol!" unless key.is_a? String or key.is_a? Symbol
|
39
|
-
raise "value should not be empty!" if @value.is_a? String and value.empty?
|
40
|
-
raise "value should not be nil!" if @value.nil?
|
41
|
-
end
|
42
|
-
|
43
|
-
# Gets the EAV row's value
|
44
|
-
def value
|
45
|
-
return nil if @value.is_a? NilPlaceholder
|
46
|
-
@value.nil? ? deserialize_value : @value
|
47
|
-
end
|
48
|
-
|
49
|
-
# Sets the EAV row's value
|
50
|
-
# @param [Object] val the value
|
51
|
-
def value= (val)
|
52
|
-
@value = (val.nil? ? NilPlaceholder.new : val)
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
def key
|
57
|
-
k = read_attribute :entry_key
|
58
|
-
(read_attribute :symbol_key) ? k.to_sym : k
|
59
|
-
end
|
60
|
-
|
61
|
-
# Raises an error if you try changing the key (unless no key is set)
|
62
|
-
def key= (val)
|
63
|
-
raise "Keys are immutable!" if read_attribute(:entry_key)
|
64
|
-
raise "Key must be a string!" unless val.is_a?(String) or val.is_a?(Symbol)
|
65
|
-
write_attribute :entry_key, val.to_s
|
66
|
-
write_attribute :symbol_key, (val.is_a? Symbol)
|
67
|
-
end
|
68
|
-
|
69
|
-
# Gets the value_type column's value for the type of value passed
|
70
|
-
# @param [Object] val the object whose value_type to determine
|
71
|
-
def self.get_value_type (val)
|
72
|
-
return nil if val.nil?
|
73
|
-
ret = SUPPORTED_TYPES[val.class.name.to_sym]
|
74
|
-
if ret.nil?
|
75
|
-
ret = SUPPORTED_TYPES[:Object]
|
76
|
-
end
|
77
|
-
ret
|
78
|
-
end
|
79
|
-
|
80
|
-
private
|
81
|
-
# Sets the value_type column to the appropriate value based on the value's type
|
82
|
-
def update_value_type
|
83
|
-
write_attribute :value_type, EavEntry.get_value_type(@value)
|
84
|
-
end
|
85
|
-
|
86
|
-
# Converts the value to its database-storable form and tells ActiveRecord that it's been changed (if it has)
|
87
|
-
def serialize_value
|
88
|
-
# Returning nil will prevent the row from being saved, to save some time since the EavHash that manages this
|
89
|
-
# entry will have marked it for deletion.
|
90
|
-
raise "Tried to save with a nil value!" if @value.nil? or @value.is_a? NilPlaceholder
|
91
|
-
|
92
|
-
update_value_type
|
93
|
-
if value_type == SUPPORTED_TYPES[:Object]
|
94
|
-
write_attribute :value, YAML::dump(@value)
|
95
|
-
else
|
96
|
-
write_attribute :value, @value.to_s
|
97
|
-
end
|
98
|
-
|
99
|
-
read_attribute :value
|
100
|
-
end
|
101
|
-
|
102
|
-
# Converts the value from it's database representation to the type specified in the value_type column.
|
103
|
-
def deserialize_value
|
104
|
-
if @value.nil?
|
105
|
-
@value = read_attribute :value
|
106
|
-
end
|
107
|
-
|
108
|
-
case value_type
|
109
|
-
when SUPPORTED_TYPES[:Object] # or Hash, Array, etc.
|
110
|
-
@value = YAML::load @value
|
111
|
-
when SUPPORTED_TYPES[:Symbol]
|
112
|
-
@value = @value.to_sym
|
113
|
-
when SUPPORTED_TYPES[:Integer] # or Fixnum, Bignum
|
114
|
-
@value = @value.to_i
|
115
|
-
when SUPPORTED_TYPES[:Float]
|
116
|
-
@value = @value.to_f
|
117
|
-
when SUPPORTED_TYPES[:Complex]
|
118
|
-
@value = Complex @value
|
119
|
-
when SUPPORTED_TYPES[:Rational]
|
120
|
-
@value = Rational @value
|
121
|
-
when SUPPORTED_TYPES[:Boolean]
|
122
|
-
@value = (@value == "true")
|
123
|
-
else
|
124
|
-
@value
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module EavHashes
|
3
|
+
# Used instead of nil when a nil value is assigned
|
4
|
+
# (otherwise, the value will try to deserialize itself and
|
5
|
+
# that would break everything horrifically)
|
6
|
+
class NilPlaceholder; end
|
7
|
+
|
8
|
+
# Represent an EAV row. This class should NOT be used directly, instead it should be inherited from
|
9
|
+
# by the class generated by eav_hash_for.
|
10
|
+
class EavEntry < ActiveRecord::Base
|
11
|
+
# prevent activerecord from thinking we're trying to do STI
|
12
|
+
self.abstract_class = true
|
13
|
+
|
14
|
+
# Tell ActiveRecord to convert the value to its DB storable format
|
15
|
+
before_save :serialize_value
|
16
|
+
|
17
|
+
# Let the key be assignable only once on creation
|
18
|
+
attr_readonly :entry_key
|
19
|
+
|
20
|
+
# Contains the values the value_type column should have based on the type of the value being stored
|
21
|
+
SUPPORTED_TYPES = {
|
22
|
+
:String => 0,
|
23
|
+
:Symbol => 1,
|
24
|
+
:Integer => 2,
|
25
|
+
:Fixnum => 2,
|
26
|
+
:Bignum => 2,
|
27
|
+
:Float => 3,
|
28
|
+
:Complex => 4,
|
29
|
+
:Rational => 5,
|
30
|
+
:Boolean => 6, # For code readability
|
31
|
+
:TrueClass => 6,
|
32
|
+
:FalseClass => 6,
|
33
|
+
:Object => 7 # anything else (including Hashes, Arrays) will be serialized to yaml and saved as Object
|
34
|
+
}
|
35
|
+
|
36
|
+
# Does some sanity checks.
|
37
|
+
def after_initialize
|
38
|
+
raise "key should be a string or symbol!" unless key.is_a? String or key.is_a? Symbol
|
39
|
+
raise "value should not be empty!" if @value.is_a? String and value.empty?
|
40
|
+
raise "value should not be nil!" if @value.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
# Gets the EAV row's value
|
44
|
+
def value
|
45
|
+
return nil if @value.is_a? NilPlaceholder
|
46
|
+
@value.nil? ? deserialize_value : @value
|
47
|
+
end
|
48
|
+
|
49
|
+
# Sets the EAV row's value
|
50
|
+
# @param [Object] val the value
|
51
|
+
def value= (val)
|
52
|
+
@value = (val.nil? ? NilPlaceholder.new : val)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def key
|
57
|
+
k = read_attribute :entry_key
|
58
|
+
(read_attribute :symbol_key) ? k.to_sym : k
|
59
|
+
end
|
60
|
+
|
61
|
+
# Raises an error if you try changing the key (unless no key is set)
|
62
|
+
def key= (val)
|
63
|
+
raise "Keys are immutable!" if read_attribute(:entry_key)
|
64
|
+
raise "Key must be a string!" unless val.is_a?(String) or val.is_a?(Symbol)
|
65
|
+
write_attribute :entry_key, val.to_s
|
66
|
+
write_attribute :symbol_key, (val.is_a? Symbol)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Gets the value_type column's value for the type of value passed
|
70
|
+
# @param [Object] val the object whose value_type to determine
|
71
|
+
def self.get_value_type (val)
|
72
|
+
return nil if val.nil?
|
73
|
+
ret = SUPPORTED_TYPES[val.class.name.to_sym]
|
74
|
+
if ret.nil?
|
75
|
+
ret = SUPPORTED_TYPES[:Object]
|
76
|
+
end
|
77
|
+
ret
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
# Sets the value_type column to the appropriate value based on the value's type
|
82
|
+
def update_value_type
|
83
|
+
write_attribute :value_type, EavEntry.get_value_type(@value)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Converts the value to its database-storable form and tells ActiveRecord that it's been changed (if it has)
|
87
|
+
def serialize_value
|
88
|
+
# Returning nil will prevent the row from being saved, to save some time since the EavHash that manages this
|
89
|
+
# entry will have marked it for deletion.
|
90
|
+
raise "Tried to save with a nil value!" if @value.nil? or @value.is_a? NilPlaceholder
|
91
|
+
|
92
|
+
update_value_type
|
93
|
+
if value_type == SUPPORTED_TYPES[:Object]
|
94
|
+
write_attribute :value, YAML::dump(@value)
|
95
|
+
else
|
96
|
+
write_attribute :value, @value.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
read_attribute :value
|
100
|
+
end
|
101
|
+
|
102
|
+
# Converts the value from it's database representation to the type specified in the value_type column.
|
103
|
+
def deserialize_value
|
104
|
+
if @value.nil?
|
105
|
+
@value = read_attribute :value
|
106
|
+
end
|
107
|
+
|
108
|
+
case value_type
|
109
|
+
when SUPPORTED_TYPES[:Object] # or Hash, Array, etc.
|
110
|
+
@value = YAML::load @value
|
111
|
+
when SUPPORTED_TYPES[:Symbol]
|
112
|
+
@value = @value.to_sym
|
113
|
+
when SUPPORTED_TYPES[:Integer] # or Fixnum, Bignum
|
114
|
+
@value = @value.to_i
|
115
|
+
when SUPPORTED_TYPES[:Float]
|
116
|
+
@value = @value.to_f
|
117
|
+
when SUPPORTED_TYPES[:Complex]
|
118
|
+
@value = Complex @value
|
119
|
+
when SUPPORTED_TYPES[:Rational]
|
120
|
+
@value = Rational @value
|
121
|
+
when SUPPORTED_TYPES[:Boolean]
|
122
|
+
@value = (@value == "true")
|
123
|
+
else
|
124
|
+
@value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
129
|
end
|
data/lib/eav_hashes/eav_hash.rb
CHANGED
@@ -1,168 +1,168 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module EavHashes
|
3
|
-
# Wraps a bunch of EavEntries and lets you use them like you would a hash
|
4
|
-
# This class should not be used directly and you should instead let eav_hash_for create one for you
|
5
|
-
class EavHash
|
6
|
-
# Creates a new EavHash. You should really let eav_hash_for do this for you...
|
7
|
-
# @param [ActiveRecord::Base] owner the Model which will own this hash
|
8
|
-
# @param [Hash] options the options hash which eav_hash generated
|
9
|
-
def initialize(owner, options)
|
10
|
-
Util::sanity_check options
|
11
|
-
@owner = owner
|
12
|
-
@options = options
|
13
|
-
end
|
14
|
-
|
15
|
-
# Saves any modified entries and deletes any which have been nil'd to save DB space
|
16
|
-
def save_entries
|
17
|
-
# The entries are lazy-loaded, so don't do anything if they haven't been accessed or modified
|
18
|
-
return unless (@entries and @changes_made)
|
19
|
-
|
20
|
-
@entries.values.each do |entry|
|
21
|
-
if entry.value.nil?
|
22
|
-
entry.delete
|
23
|
-
else
|
24
|
-
set_entry_owner(entry)
|
25
|
-
entry.save
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# Gets the value of an EAV attribute
|
31
|
-
# @param [String, Symbol] key
|
32
|
-
def [](key)
|
33
|
-
raise "Key must be a string or a symbol!" unless key.is_a?(String) or key.is_a?(Symbol)
|
34
|
-
load_entries_if_needed
|
35
|
-
return @entries[key].value if @entries[key]
|
36
|
-
nil
|
37
|
-
end
|
38
|
-
|
39
|
-
# Sets the value of the EAV attribute `key` to `value`
|
40
|
-
# @param [String, Symbol] key the attribute
|
41
|
-
# @param [Object] value the value
|
42
|
-
def []=(key, value)
|
43
|
-
update_or_create_entry key, value
|
44
|
-
end
|
45
|
-
|
46
|
-
# I don't know why Ruby hashes don't have a shovel operator, but I will make damn sure that I
|
47
|
-
# fight the power and stick it to the man by implementing it.
|
48
|
-
# @param [Hash, EavHash] dirt the dirt to shovel (ba dum, tss)
|
49
|
-
def <<(dirt)
|
50
|
-
if dirt.is_a? Hash
|
51
|
-
dirt.each do |key, value|
|
52
|
-
update_or_create_entry key, value
|
53
|
-
end
|
54
|
-
elsif dirt.is_a? EavHash
|
55
|
-
dirt.entries.each do |key, entry|
|
56
|
-
update_or_create_entry key, entry.value
|
57
|
-
end
|
58
|
-
else
|
59
|
-
raise "You can't shovel something that's not a Hash or EavHash here!"
|
60
|
-
end
|
61
|
-
|
62
|
-
self
|
63
|
-
end
|
64
|
-
|
65
|
-
# Gets the raw hash containing EavEntries by their keys
|
66
|
-
def entries
|
67
|
-
load_entries_if_needed
|
68
|
-
end
|
69
|
-
|
70
|
-
# Gets the actual values this EavHash contains
|
71
|
-
def values
|
72
|
-
load_entries_if_needed
|
73
|
-
|
74
|
-
ret = []
|
75
|
-
@entries.values.each do |value|
|
76
|
-
ret << value
|
77
|
-
end
|
78
|
-
|
79
|
-
ret
|
80
|
-
end
|
81
|
-
|
82
|
-
# Gets the keys this EavHash manages
|
83
|
-
def keys
|
84
|
-
load_entries_if_needed
|
85
|
-
@entries.keys
|
86
|
-
end
|
87
|
-
|
88
|
-
# Emulates Hash.each
|
89
|
-
def each (&block)
|
90
|
-
as_hash.each block
|
91
|
-
end
|
92
|
-
|
93
|
-
# Emulates Hash.each_pair (same as each)
|
94
|
-
def each_pair (&block)
|
95
|
-
each &block
|
96
|
-
end
|
97
|
-
|
98
|
-
# Empties the hash by setting all the values to nil
|
99
|
-
# (without committing them, of course)
|
100
|
-
def clear
|
101
|
-
load_entries_if_needed
|
102
|
-
@entries.each do |_, entry|
|
103
|
-
entry.value = nil
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# Returns a hash with each entry key mapped to its actual value,
|
108
|
-
# not the internal EavEntry
|
109
|
-
def as_hash
|
110
|
-
load_entries_if_needed
|
111
|
-
hsh = {}
|
112
|
-
@entries.each do |k, entry|
|
113
|
-
hsh[k] = entry.value
|
114
|
-
end
|
115
|
-
|
116
|
-
hsh
|
117
|
-
end
|
118
|
-
|
119
|
-
# Take the crap out of #inspect calls
|
120
|
-
def inspect
|
121
|
-
as_hash
|
122
|
-
end
|
123
|
-
|
124
|
-
private
|
125
|
-
def update_or_create_entry(key, value)
|
126
|
-
raise "Key must be a string or a symbol!" unless key.is_a?(String) or key.is_a?(Symbol)
|
127
|
-
load_entries_if_needed
|
128
|
-
|
129
|
-
@changes_made = true
|
130
|
-
@owner.updated_at = Time.now
|
131
|
-
|
132
|
-
if @entries[key]
|
133
|
-
@entries[key].value = value
|
134
|
-
else
|
135
|
-
new_entry = @options[:entry_class].new
|
136
|
-
set_entry_owner(new_entry)
|
137
|
-
new_entry.key = key
|
138
|
-
new_entry.value = value
|
139
|
-
|
140
|
-
@entries[key] = new_entry
|
141
|
-
|
142
|
-
value
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
# Since entries are lazy-loaded, this is called just before an operation on an entry happens and
|
147
|
-
# loads the rows only once per EavHash lifetime.
|
148
|
-
def load_entries_if_needed
|
149
|
-
if @entries.nil?
|
150
|
-
@entries = {}
|
151
|
-
rows_from_model = @owner.send("#{@options[:entry_assoc_name]}")
|
152
|
-
rows_from_model.each do |row|
|
153
|
-
@entries[row.key] = row
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
@entries
|
158
|
-
end
|
159
|
-
|
160
|
-
# Sets an entry's owner ID. This is called when we save attributes for a model which has just been
|
161
|
-
# created and not committed to the DB prior to having its EAV hash(es) modified
|
162
|
-
# @param [EavEntry] entry the entry whose owner to change
|
163
|
-
def set_entry_owner(entry)
|
164
|
-
entry.send "#{@options[:parent_assoc_name]}_id=", @owner.id
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
1
|
+
module ActiveRecord
|
2
|
+
module EavHashes
|
3
|
+
# Wraps a bunch of EavEntries and lets you use them like you would a hash
|
4
|
+
# This class should not be used directly and you should instead let eav_hash_for create one for you
|
5
|
+
class EavHash
|
6
|
+
# Creates a new EavHash. You should really let eav_hash_for do this for you...
|
7
|
+
# @param [ActiveRecord::Base] owner the Model which will own this hash
|
8
|
+
# @param [Hash] options the options hash which eav_hash generated
|
9
|
+
def initialize(owner, options)
|
10
|
+
Util::sanity_check options
|
11
|
+
@owner = owner
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# Saves any modified entries and deletes any which have been nil'd to save DB space
|
16
|
+
def save_entries
|
17
|
+
# The entries are lazy-loaded, so don't do anything if they haven't been accessed or modified
|
18
|
+
return unless (@entries and @changes_made)
|
19
|
+
|
20
|
+
@entries.values.each do |entry|
|
21
|
+
if entry.value.nil?
|
22
|
+
entry.delete
|
23
|
+
else
|
24
|
+
set_entry_owner(entry)
|
25
|
+
entry.save
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Gets the value of an EAV attribute
|
31
|
+
# @param [String, Symbol] key
|
32
|
+
def [](key)
|
33
|
+
raise "Key must be a string or a symbol!" unless key.is_a?(String) or key.is_a?(Symbol)
|
34
|
+
load_entries_if_needed
|
35
|
+
return @entries[key].value if @entries[key]
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sets the value of the EAV attribute `key` to `value`
|
40
|
+
# @param [String, Symbol] key the attribute
|
41
|
+
# @param [Object] value the value
|
42
|
+
def []=(key, value)
|
43
|
+
update_or_create_entry key, value
|
44
|
+
end
|
45
|
+
|
46
|
+
# I don't know why Ruby hashes don't have a shovel operator, but I will make damn sure that I
|
47
|
+
# fight the power and stick it to the man by implementing it.
|
48
|
+
# @param [Hash, EavHash] dirt the dirt to shovel (ba dum, tss)
|
49
|
+
def <<(dirt)
|
50
|
+
if dirt.is_a? Hash
|
51
|
+
dirt.each do |key, value|
|
52
|
+
update_or_create_entry key, value
|
53
|
+
end
|
54
|
+
elsif dirt.is_a? EavHash
|
55
|
+
dirt.entries.each do |key, entry|
|
56
|
+
update_or_create_entry key, entry.value
|
57
|
+
end
|
58
|
+
else
|
59
|
+
raise "You can't shovel something that's not a Hash or EavHash here!"
|
60
|
+
end
|
61
|
+
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Gets the raw hash containing EavEntries by their keys
|
66
|
+
def entries
|
67
|
+
load_entries_if_needed
|
68
|
+
end
|
69
|
+
|
70
|
+
# Gets the actual values this EavHash contains
|
71
|
+
def values
|
72
|
+
load_entries_if_needed
|
73
|
+
|
74
|
+
ret = []
|
75
|
+
@entries.values.each do |value|
|
76
|
+
ret << value
|
77
|
+
end
|
78
|
+
|
79
|
+
ret
|
80
|
+
end
|
81
|
+
|
82
|
+
# Gets the keys this EavHash manages
|
83
|
+
def keys
|
84
|
+
load_entries_if_needed
|
85
|
+
@entries.keys
|
86
|
+
end
|
87
|
+
|
88
|
+
# Emulates Hash.each
|
89
|
+
def each (&block)
|
90
|
+
as_hash.each block
|
91
|
+
end
|
92
|
+
|
93
|
+
# Emulates Hash.each_pair (same as each)
|
94
|
+
def each_pair (&block)
|
95
|
+
each &block
|
96
|
+
end
|
97
|
+
|
98
|
+
# Empties the hash by setting all the values to nil
|
99
|
+
# (without committing them, of course)
|
100
|
+
def clear
|
101
|
+
load_entries_if_needed
|
102
|
+
@entries.each do |_, entry|
|
103
|
+
entry.value = nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns a hash with each entry key mapped to its actual value,
|
108
|
+
# not the internal EavEntry
|
109
|
+
def as_hash
|
110
|
+
load_entries_if_needed
|
111
|
+
hsh = {}
|
112
|
+
@entries.each do |k, entry|
|
113
|
+
hsh[k] = entry.value
|
114
|
+
end
|
115
|
+
|
116
|
+
hsh
|
117
|
+
end
|
118
|
+
|
119
|
+
# Take the crap out of #inspect calls
|
120
|
+
def inspect
|
121
|
+
as_hash
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def update_or_create_entry(key, value)
|
126
|
+
raise "Key must be a string or a symbol!" unless key.is_a?(String) or key.is_a?(Symbol)
|
127
|
+
load_entries_if_needed
|
128
|
+
|
129
|
+
@changes_made = true
|
130
|
+
@owner.updated_at = Time.now
|
131
|
+
|
132
|
+
if @entries[key]
|
133
|
+
@entries[key].value = value
|
134
|
+
else
|
135
|
+
new_entry = @options[:entry_class].new
|
136
|
+
set_entry_owner(new_entry)
|
137
|
+
new_entry.key = key
|
138
|
+
new_entry.value = value
|
139
|
+
|
140
|
+
@entries[key] = new_entry
|
141
|
+
|
142
|
+
value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Since entries are lazy-loaded, this is called just before an operation on an entry happens and
|
147
|
+
# loads the rows only once per EavHash lifetime.
|
148
|
+
def load_entries_if_needed
|
149
|
+
if @entries.nil?
|
150
|
+
@entries = {}
|
151
|
+
rows_from_model = @owner.send("#{@options[:entry_assoc_name]}")
|
152
|
+
rows_from_model.each do |row|
|
153
|
+
@entries[row.key] = row
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
@entries
|
158
|
+
end
|
159
|
+
|
160
|
+
# Sets an entry's owner ID. This is called when we save attributes for a model which has just been
|
161
|
+
# created and not committed to the DB prior to having its EAV hash(es) modified
|
162
|
+
# @param [EavEntry] entry the entry whose owner to change
|
163
|
+
def set_entry_owner(entry)
|
164
|
+
entry.send "#{@options[:parent_assoc_name]}_id=", @owner.id
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
168
|
end
|