store_field 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +40 -13
- data/lib/store_field.rb +29 -13
- data/lib/store_field/version.rb +1 -1
- data/spec/store_field_spec.rb +40 -19
- metadata +3 -3
data/README.md
CHANGED
@@ -21,6 +21,10 @@ The former is bad because the TEXT (or BLOB) column type could be stored off-pag
|
|
21
21
|
|
22
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
|
+
Changelog:
|
25
|
+
|
26
|
+
* v2.0.0: Hash-type supports `keys` option to add accessors for validation. Set-type with `values` option now adds a validation rather than raising an exception.
|
27
|
+
|
24
28
|
## Usage
|
25
29
|
|
26
30
|
Add this line to your application's Gemfile.
|
@@ -45,17 +49,34 @@ user = User.new
|
|
45
49
|
user.tutorials[:quick_start] = :finished
|
46
50
|
```
|
47
51
|
|
48
|
-
When no option is given, it defaults to the first serialized column, using
|
52
|
+
When no option is given, it defaults to the first serialized column, using Hash-type. So `store_field :tutorials` is equivalent to the following.
|
49
53
|
|
50
54
|
```ruby
|
51
55
|
store_field :tutorials, in: :storage, type: Hash
|
52
56
|
```
|
53
57
|
|
54
|
-
##
|
58
|
+
## Hash-type features
|
59
|
+
|
60
|
+
When the `keys` option is given for Hash-type, convenience accessors are automatically defined, which can be used for validation.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
class User < ActiveRecord::Base
|
64
|
+
store_field :tutorials, keys: [ :quick_start ]
|
65
|
+
|
66
|
+
validates :tutorials_quick_start, inclusion: { in: [ :started, :finished ], allow_nil: true }
|
67
|
+
end
|
68
|
+
|
69
|
+
user = User.new
|
70
|
+
user.tutorials_quick_start = :started
|
71
|
+
user.valid?
|
72
|
+
=> true
|
73
|
+
```
|
74
|
+
|
75
|
+
## Set-type features
|
55
76
|
|
56
|
-
In addition to
|
77
|
+
In addition to Hash-type, StoreField supports Set-type. To use Set-type, simply pass `type: Set` option.
|
57
78
|
|
58
|
-
It turns out that Set is extremely useful most of the time when you think what you need is `Array`.
|
79
|
+
It turns out that Set-type is extremely useful most of the time when you think what you need is `Array`.
|
59
80
|
|
60
81
|
```ruby
|
61
82
|
store_field :funnel, type: Set
|
@@ -78,21 +99,22 @@ cart.funnel # => #<Set: {:add_item, :checkout}>
|
|
78
99
|
cart.set_funnel(:checkout).save! # => true
|
79
100
|
```
|
80
101
|
|
81
|
-
Also you can enumerate acceptable values
|
102
|
+
Also you can enumerate acceptable values for validation.
|
82
103
|
|
83
104
|
```ruby
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
With the definition above, the following code will raise an exception.
|
105
|
+
class Cart < ActiveRecord::Base
|
106
|
+
store_field :funnel, type: Set, values: [ :add_item, :checkout ]
|
107
|
+
end
|
88
108
|
|
89
|
-
|
90
|
-
set_funnel(:bogus)
|
109
|
+
cart = Cart.new
|
110
|
+
cart.set_funnel(:bogus)
|
111
|
+
cart.valid?
|
112
|
+
=> false
|
91
113
|
```
|
92
114
|
|
93
|
-
## Use cases for the Set
|
115
|
+
## Use cases for the Set-type
|
94
116
|
|
95
|
-
Set is a great way to store an arbitrary number of named states.
|
117
|
+
Set-type is a great way to store an arbitrary number of named states.
|
96
118
|
|
97
119
|
Consider you have a system that sends an alert when some criteria have been met.
|
98
120
|
|
@@ -139,3 +161,8 @@ YAML::ENGINE.yamler # => "psych"
|
|
139
161
|
```
|
140
162
|
|
141
163
|
If you are using Ruby 1.9.2 or later, `psych` should be used by default.
|
164
|
+
|
165
|
+
## Other Solutions
|
166
|
+
|
167
|
+
* [StoreConfigurable](https://github.com/metaskills/store_configurable) - A zero-configuration recursive Hash for storing a tree of options.
|
168
|
+
|
data/lib/store_field.rb
CHANGED
@@ -7,35 +7,51 @@ module StoreField
|
|
7
7
|
|
8
8
|
module ClassMethods
|
9
9
|
def store_field(key, options = {})
|
10
|
-
raise ArgumentError
|
11
|
-
raise ArgumentError
|
12
|
-
raise ArgumentError.new(':values is invalid') if options[:values] and !options[:values].is_a?(Array)
|
10
|
+
raise ArgumentError, ':in is invalid' if options[:in] and serialized_attributes[options[:in].to_s].nil?
|
11
|
+
raise ArgumentError, ':type is invalid' if options[:type] and ![ Hash, Set ].include?(options[:type])
|
13
12
|
|
14
|
-
klass = options[:type]
|
15
|
-
values = options[:values]
|
13
|
+
klass = options[:type] || Hash
|
16
14
|
store_attribute = options[:in] || serialized_attributes.keys.first
|
17
|
-
raise
|
15
|
+
raise ScriptError, 'store method must be defined before store_field' if store_attribute.nil?
|
18
16
|
|
19
17
|
# Accessor
|
20
18
|
define_method(key) do
|
21
19
|
value = send(store_attribute)[key]
|
22
20
|
if value.nil?
|
23
|
-
value = klass
|
21
|
+
value = klass.new
|
24
22
|
send(store_attribute)[key] = value
|
25
23
|
end
|
26
24
|
value
|
27
25
|
end
|
28
26
|
|
29
|
-
# Utility methods for
|
30
|
-
if options[:
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
# Utility methods for Hash
|
28
|
+
if klass == Hash and options[:keys]
|
29
|
+
options[:keys].each do |subkey|
|
30
|
+
define_method("#{key}_#{subkey}") do
|
31
|
+
send(key)[subkey]
|
32
|
+
end
|
33
|
+
|
34
|
+
define_method("#{key}_#{subkey}=") do |value|
|
35
|
+
send(key)[subkey] = value
|
36
|
+
end
|
35
37
|
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Utility methods for Set
|
41
|
+
if klass == Set
|
42
|
+
define_method("set_#{key}") {|value| send(key).add(value); self }
|
36
43
|
define_method("unset_#{key}") {|value| send(key).delete(value); self }
|
37
44
|
define_method("set_#{key}?") {|value| send(key).include?(value) }
|
38
45
|
define_method("unset_#{key}?") {|value| !send(key).include?(value) }
|
46
|
+
|
47
|
+
if options[:values]
|
48
|
+
validate do
|
49
|
+
diff = send(key).to_a - options[:values]
|
50
|
+
unless diff.empty?
|
51
|
+
errors.add(key, "is invalid with #{diff.inspect}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
39
55
|
end
|
40
56
|
end
|
41
57
|
end
|
data/lib/store_field/version.rb
CHANGED
data/spec/store_field_spec.rb
CHANGED
@@ -6,8 +6,10 @@ end
|
|
6
6
|
|
7
7
|
class User < ActiveRecord::Base
|
8
8
|
store :storage
|
9
|
-
store_field :tutorials
|
9
|
+
store_field :tutorials, keys: [ :quick_start ]
|
10
10
|
store_field :delivered, type: Set, values: [ :welcome, :balance_low ]
|
11
|
+
|
12
|
+
validates :tutorials_quick_start, inclusion: { in: [ :started, :finished ], allow_nil: true }
|
11
13
|
end
|
12
14
|
|
13
15
|
describe StoreField do
|
@@ -16,8 +18,8 @@ describe StoreField do
|
|
16
18
|
end
|
17
19
|
|
18
20
|
it 'raises when store is not defined beforehand' do
|
19
|
-
expect { Class.new(ActiveRecord::Base) { store :storage; store_field :delivered } }.to_not raise_error(
|
20
|
-
expect { Class.new(ActiveRecord::Base) { store_field :delivered } }.to raise_error(
|
21
|
+
expect { Class.new(ActiveRecord::Base) { store :storage; store_field :delivered } }.to_not raise_error(ScriptError)
|
22
|
+
expect { Class.new(ActiveRecord::Base) { store_field :delivered } }.to raise_error(ScriptError)
|
21
23
|
end
|
22
24
|
|
23
25
|
it 'raises when invalid option is given' do
|
@@ -28,29 +30,48 @@ describe StoreField do
|
|
28
30
|
it 'initializes with the specified type' do
|
29
31
|
@user.tutorials.should == {}
|
30
32
|
@user.delivered.should == Set.new
|
33
|
+
@user.valid?.should == true
|
31
34
|
end
|
32
35
|
|
33
|
-
|
34
|
-
|
35
|
-
@user.
|
36
|
-
|
36
|
+
describe Hash do
|
37
|
+
it 'validates Hash' do
|
38
|
+
@user.tutorials_quick_start = :started
|
39
|
+
@user.valid?.should == true
|
40
|
+
@user.errors.empty?.should == true
|
41
|
+
|
42
|
+
@user.tutorials_quick_start = :bogus
|
43
|
+
@user.valid?.should == false
|
44
|
+
@user.errors.has_key?(:tutorials_quick_start).should == true
|
45
|
+
end
|
37
46
|
end
|
38
47
|
|
39
|
-
|
40
|
-
|
48
|
+
describe Set do
|
49
|
+
it 'validates Set' do
|
50
|
+
@user.set_delivered(:welcome)
|
51
|
+
@user.valid?.should == true
|
52
|
+
@user.errors.empty?.should == true
|
41
53
|
|
42
|
-
|
43
|
-
|
54
|
+
@user.set_delivered(:bogus)
|
55
|
+
@user.valid?.should == false
|
56
|
+
@user.errors.has_key?(:delivered).should == true
|
57
|
+
end
|
44
58
|
|
45
|
-
|
46
|
-
|
59
|
+
it 'sets and unsets keywords' do
|
60
|
+
@user.set_delivered(:welcome)
|
47
61
|
|
48
|
-
|
49
|
-
|
62
|
+
# Consume balance, notify once and only once
|
63
|
+
@user.set_delivered(:balance_low)
|
64
|
+
|
65
|
+
# Another deposit, restore balance
|
66
|
+
@user.unset_delivered(:balance_low)
|
67
|
+
|
68
|
+
@user.delivered.should == Set.new([:welcome])
|
69
|
+
end
|
50
70
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
71
|
+
it 'saves in-line' do
|
72
|
+
@user.set_delivered(:welcome).save.should == true
|
73
|
+
@user.reload
|
74
|
+
@user.set_delivered?(:welcome).should == true
|
75
|
+
end
|
55
76
|
end
|
56
77
|
end
|
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:
|
4
|
+
version: 2.0.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-
|
12
|
+
date: 2012-11-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -98,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
98
|
version: '0'
|
99
99
|
requirements: []
|
100
100
|
rubyforge_project:
|
101
|
-
rubygems_version: 1.8.
|
101
|
+
rubygems_version: 1.8.24
|
102
102
|
signing_key:
|
103
103
|
specification_version: 3
|
104
104
|
summary: Nested fields for ActiveRecord::Store
|