store_field 1.1.0 → 2.0.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 +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
|