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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -20
  3. data/README.md +147 -147
  4. data/Rakefile +30 -30
  5. data/lib/eav_hashes/activerecord_extension.rb +37 -37
  6. data/lib/eav_hashes/eav_entry.rb +128 -128
  7. data/lib/eav_hashes/eav_hash.rb +167 -167
  8. data/lib/eav_hashes/util.rb +122 -122
  9. data/lib/eav_hashes/version.rb +5 -5
  10. data/lib/eav_hashes.rb +8 -8
  11. data/lib/generators/eav_migration/USAGE +26 -26
  12. data/lib/generators/eav_migration/eav_migration.rb +35 -35
  13. data/lib/generators/eav_migration/templates/eav_migration.erb +15 -15
  14. data/spec/dummy/README.rdoc +261 -261
  15. data/spec/dummy/Rakefile +7 -7
  16. data/spec/dummy/app/assets/javascripts/application.js +15 -15
  17. data/spec/dummy/app/assets/stylesheets/application.css +13 -13
  18. data/spec/dummy/app/controllers/application_controller.rb +3 -3
  19. data/spec/dummy/app/helpers/application_helper.rb +2 -2
  20. data/spec/dummy/app/models/custom_test_object.rb +6 -6
  21. data/spec/dummy/app/models/product.rb +3 -4
  22. data/spec/dummy/app/views/layouts/application.html.erb +14 -14
  23. data/spec/dummy/config/application.rb +62 -68
  24. data/spec/dummy/config/boot.rb +9 -9
  25. data/spec/dummy/config/database.yml +25 -25
  26. data/spec/dummy/config/environment.rb +5 -5
  27. data/spec/dummy/config/environments/development.rb +34 -37
  28. data/spec/dummy/config/environments/production.rb +70 -67
  29. data/spec/dummy/config/environments/test.rb +34 -37
  30. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -7
  31. data/spec/dummy/config/initializers/inflections.rb +15 -15
  32. data/spec/dummy/config/initializers/mime_types.rb +5 -5
  33. data/spec/dummy/config/initializers/secret_token.rb +7 -7
  34. data/spec/dummy/config/initializers/session_store.rb +8 -8
  35. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -14
  36. data/spec/dummy/config/locales/en.yml +5 -5
  37. data/spec/dummy/config/routes.rb +58 -58
  38. data/spec/dummy/config.ru +4 -4
  39. data/spec/dummy/db/migrate/20121206133059_create_products.rb +9 -9
  40. data/spec/dummy/db/migrate/20121210055854_create_product_tech_specs.rb +15 -15
  41. data/spec/dummy/db/seeds.rb +30 -30
  42. data/spec/dummy/public/404.html +26 -26
  43. data/spec/dummy/public/422.html +26 -26
  44. data/spec/dummy/public/500.html +25 -25
  45. data/spec/dummy/script/rails +6 -6
  46. data/spec/lib/eav_hashes/eav_hash_spec.rb +147 -137
  47. data/spec/lib/generators/eav_migration_spec.rb +60 -60
  48. data/spec/spec_helper.rb +24 -24
  49. metadata +25 -44
  50. data/spec/dummy/db/development.sqlite3 +0 -0
  51. data/spec/dummy/db/schema.rb +0 -35
  52. data/spec/dummy/db/test.sqlite3 +0 -0
  53. data/spec/dummy/log/ENV=test.log +0 -1
  54. data/spec/dummy/log/development.log +0 -46
  55. data/spec/dummy/log/test.log +0 -4623
@@ -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
@@ -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