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 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