eav_hashes 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|