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 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 the `Hash` datatype. So `store_field :tutorials` is equivalent to the following.
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
- ## Typing support for Set
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 `Hash`, StoreField supports the `Set` data type. To use Set, simply pass `type: Set` option.
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, which is validated on the `set_[field]` method.
102
+ Also you can enumerate acceptable values for validation.
82
103
 
83
104
  ```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.
105
+ class Cart < ActiveRecord::Base
106
+ store_field :funnel, type: Set, values: [ :add_item, :checkout ]
107
+ end
88
108
 
89
- ```ruby
90
- set_funnel(:bogus) # => ArgumentError: :bogus is not allowed
109
+ cart = Cart.new
110
+ cart.set_funnel(:bogus)
111
+ cart.valid?
112
+ => false
91
113
  ```
92
114
 
93
- ## Use cases for the Set type
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.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)
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 ArgumentError.new('store method must be defined before store_field') if store_attribute.nil?
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 ? klass.new : {}
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 Set
30
- if options[:type] == Set
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
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
@@ -1,3 +1,3 @@
1
1
  module StoreField
2
- VERSION = '1.1.0'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -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(ArgumentError)
20
- expect { Class.new(ActiveRecord::Base) { store_field :delivered } }.to raise_error(ArgumentError)
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
- it 'raises when invalid value is given for Set' do
34
- expect {
35
- @user.set_delivered(:bogus)
36
- }.to raise_error(ArgumentError)
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
- it 'sets and unsets keywords' do
40
- @user.set_delivered(:welcome)
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
- # Consume balance, notify once and only once
43
- @user.set_delivered(:balance_low)
54
+ @user.set_delivered(:bogus)
55
+ @user.valid?.should == false
56
+ @user.errors.has_key?(:delivered).should == true
57
+ end
44
58
 
45
- # Another deposit, restore balance
46
- @user.unset_delivered(:balance_low)
59
+ it 'sets and unsets keywords' do
60
+ @user.set_delivered(:welcome)
47
61
 
48
- @user.delivered.should == Set.new([:welcome])
49
- end
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
- it 'saves in-line' do
52
- @user.set_delivered(:welcome).save.should == true
53
- @user.reload
54
- @user.set_delivered?(:welcome).should == true
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: 1.1.0
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-09-21 00:00:00.000000000 Z
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.19
101
+ rubygems_version: 1.8.24
102
102
  signing_key:
103
103
  specification_version: 3
104
104
  summary: Nested fields for ActiveRecord::Store