store_field 1.0.0 → 1.1.0

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.
data/README.md CHANGED
@@ -17,9 +17,9 @@ user.tutorials[:quick_start] = :visited # => NoMethodError: undefined method
17
17
 
18
18
  There are two ways to solve this problem - a. break down `options` into multiple columns like `tutorials` and `preference`, or b. define an accessor method for each to initialize with an empty `Hash` when accessed for the first time.
19
19
 
20
- The former is bad because the TEXT (or BLOB) column type could be [stored off-page](http://www.mysqlperformanceblog.com/2010/02/09/blob-storage-in-innodb/) when it gets big and you could hit some strange bugs and/or performance penalty. Furthermore, adding columns kills the primary purpose of having key-value store - you use this feature because you don't like migrations, right? So it's two-fold bad.
20
+ The former is bad because the TEXT (or BLOB) column type could be stored off-page when it gets big and you could hit some [strange bugs and/or performance penalty](http://www.mysqlperformanceblog.com/2010/02/09/blob-storage-in-innodb/). Furthermore, adding columns kills the primary purpose of having key-value store - you use this feature because you don't like migrations, right? So it's two-fold bad.
21
21
 
22
- StoreField takes the latter approach. It defines accessors that initializes with an empty `Hash` or `Set` automatically. Now you have a single TEXT column for everything!
22
+ StoreField takes the latter approach. It defines accessors that initialize with an empty `Hash` or `Set` automatically. Now you have a single TEXT column for everything!
23
23
 
24
24
  ## Usage
25
25
 
@@ -78,9 +78,21 @@ cart.funnel # => #<Set: {:add_item, :checkout}>
78
78
  cart.set_funnel(:checkout).save! # => true
79
79
  ```
80
80
 
81
+ Also you can enumerate acceptable values, which is validated on the `set_[field]` method.
82
+
83
+ ```ruby
84
+ store_field :funnel, type: Set, values: [ :add_item, :checkout ]
85
+ ```
86
+
87
+ With the definition above, the following code will raise an exception.
88
+
89
+ ```ruby
90
+ set_funnel(:bogus) # => ArgumentError: :bogus is not allowed
91
+ ```
92
+
81
93
  ## Use cases for the Set type
82
94
 
83
- Set is a great way to store an arbitrary number of states.
95
+ Set is a great way to store an arbitrary number of named states.
84
96
 
85
97
  Consider you have a system that sends an alert when some criteria have been met.
86
98
 
@@ -105,3 +117,25 @@ end
105
117
  ```
106
118
 
107
119
  That way, the user won't receive the same alert again, until `unset_delivered` is called when the next billing cycle starts.
120
+
121
+ ## YAML serialization
122
+
123
+ ActiveRecord::Store uses YAML to serialize Ruby objects. A StoreField will be stored as follows:
124
+
125
+ ```yaml
126
+ ---
127
+ :funnel: !ruby/object:Set
128
+ hash:
129
+ :add_item: true
130
+ :checkout: true
131
+ ```
132
+
133
+ As you can see, the `Set` class internally uses `Hash` for its storage.
134
+
135
+ There is a [known compatibility problem](http://bugs.ruby-lang.org/issues/6910) between `psych` and `syck`, be sure to use `psych` from the beginning.
136
+
137
+ ```ruby
138
+ YAML::ENGINE.yamler # => "psych"
139
+ ```
140
+
141
+ If you are using Ruby 1.9.2 or later, `psych` should be used by default.
@@ -1,3 +1,3 @@
1
1
  module StoreField
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
data/lib/store_field.rb CHANGED
@@ -7,26 +7,32 @@ module StoreField
7
7
 
8
8
  module ClassMethods
9
9
  def store_field(key, options = {})
10
- raise ArgumentError.new(':in is invalid') if options[:in] and serialized_attributes[options[:in].to_s].nil?
11
- raise ArgumentError.new(':type is invalid') if options[:type] and ![ Hash, Set ].include?(options[:type])
10
+ raise ArgumentError.new(':in is invalid') if options[:in] and serialized_attributes[options[:in].to_s].nil?
11
+ raise ArgumentError.new(':type is invalid') if options[:type] and ![ Hash, Set ].include?(options[:type])
12
+ raise ArgumentError.new(':values is invalid') if options[:values] and !options[:values].is_a?(Array)
12
13
 
13
14
  klass = options[:type]
15
+ values = options[:values]
14
16
  store_attribute = options[:in] || serialized_attributes.keys.first
15
17
  raise ArgumentError.new('store method must be defined before store_field') if store_attribute.nil?
16
18
 
17
19
  # Accessor
18
20
  define_method(key) do
19
- value = send("#{store_attribute}")[key]
21
+ value = send(store_attribute)[key]
20
22
  if value.nil?
21
23
  value = klass ? klass.new : {}
22
- send("#{store_attribute}")[key] = value
24
+ send(store_attribute)[key] = value
23
25
  end
24
26
  value
25
27
  end
26
28
 
27
29
  # Utility methods for Set
28
30
  if options[:type] == Set
29
- define_method("set_#{key}") {|value| send(key).add(value); self }
31
+ define_method("set_#{key}") do |value|
32
+ raise ArgumentError.new("#{value.inspect} is not allowed") if values and !values.include?(value)
33
+ send(key).add(value)
34
+ self
35
+ end
30
36
  define_method("unset_#{key}") {|value| send(key).delete(value); self }
31
37
  define_method("set_#{key}?") {|value| send(key).include?(value) }
32
38
  define_method("unset_#{key}?") {|value| !send(key).include?(value) }
@@ -7,7 +7,7 @@ end
7
7
  class User < ActiveRecord::Base
8
8
  store :storage
9
9
  store_field :tutorials
10
- store_field :delivered, type: Set
10
+ store_field :delivered, type: Set, values: [ :welcome, :balance_low ]
11
11
  end
12
12
 
13
13
  describe StoreField do
@@ -30,19 +30,22 @@ describe StoreField do
30
30
  @user.delivered.should == Set.new
31
31
  end
32
32
 
33
+ it 'raises when invalid value is given for Set' do
34
+ expect {
35
+ @user.set_delivered(:bogus)
36
+ }.to raise_error(ArgumentError)
37
+ end
38
+
33
39
  it 'sets and unsets keywords' do
34
40
  @user.set_delivered(:welcome)
35
- @user.set_delivered(:first_deposit)
36
41
 
37
42
  # Consume balance, notify once and only once
38
43
  @user.set_delivered(:balance_low)
39
- @user.set_delivered(:balance_negative)
40
44
 
41
45
  # Another deposit, restore balance
42
46
  @user.unset_delivered(:balance_low)
43
- @user.unset_delivered(:balance_negative)
44
47
 
45
- @user.delivered.should == Set.new([:welcome, :first_deposit])
48
+ @user.delivered.should == Set.new([:welcome])
46
49
  end
47
50
 
48
51
  it 'saves in-line' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: store_field
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-19 00:00:00.000000000 Z
12
+ date: 2012-09-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord