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 +37 -3
- data/lib/store_field/version.rb +1 -1
- data/lib/store_field.rb +11 -5
- data/spec/store_field_spec.rb +8 -5
- metadata +2 -2
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
|
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
|
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.
|
data/lib/store_field/version.rb
CHANGED
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')
|
11
|
-
raise ArgumentError.new(':type is invalid')
|
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(
|
21
|
+
value = send(store_attribute)[key]
|
20
22
|
if value.nil?
|
21
23
|
value = klass ? klass.new : {}
|
22
|
-
send(
|
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}")
|
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) }
|
data/spec/store_field_spec.rb
CHANGED
@@ -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
|
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.
|
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-
|
12
|
+
date: 2012-09-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|