property_sets 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +77 -58
- data/lib/property_sets/action_view_extension.rb +20 -17
- data/property_sets.gemspec +1 -1
- data/test/test_view_extensions.rb +11 -2
- metadata +2 -2
data/README.md
CHANGED
@@ -6,112 +6,131 @@ This gem is a way for you to use a basic "key/value" store for storing attribute
|
|
6
6
|
|
7
7
|
You configure the allowed stored properties by specifying these in the model:
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
```ruby
|
10
|
+
class Account < ActiveRecord::Base
|
11
|
+
property_set :settings do
|
12
|
+
property :version, :default => "v1.0"
|
13
|
+
property :featured, :protected => true
|
14
|
+
property :activated
|
15
|
+
end
|
16
|
+
|
17
|
+
property_set :texts do
|
18
|
+
property :epilogue
|
19
|
+
end
|
20
|
+
end
|
21
|
+
```
|
20
22
|
|
21
23
|
The declared properties can then be accessed runtime via the defined association:
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
```ruby
|
26
|
+
# Return the value of the version record for this account, or the default value if not set
|
27
|
+
account.settings.version
|
25
28
|
|
26
|
-
|
27
|
-
|
29
|
+
# Update the version record with given value
|
30
|
+
account.settings.version = "v1.1"
|
28
31
|
|
29
|
-
|
30
|
-
|
32
|
+
# Query the truth value of the property
|
33
|
+
account.settings.featured?
|
31
34
|
|
32
|
-
|
33
|
-
|
35
|
+
# Short hand for setting one or more values
|
36
|
+
account.settings.set(:version => "v1.2", :activated => true)
|
37
|
+
```
|
34
38
|
|
35
39
|
### Validations
|
36
40
|
|
37
41
|
Property sets supports standard AR validations, although in a somewhat manual fashion.
|
38
42
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
+
```ruby
|
44
|
+
class Account < ActiveRecord::Base
|
45
|
+
property_set :settings do
|
46
|
+
property :version, :default => "v1.0"
|
47
|
+
property :featured, :protected => true
|
43
48
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
validates_format_of :value, :with => /v\d+\.\d+/, :message => "of version is invalid",
|
50
|
+
:if => Proc.new { |r| r.name.to_sym == :version }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
48
54
|
|
49
|
-
On
|
50
|
-
setting record using
|
55
|
+
On `account.save` this will result in an error record being added. You can also inspect the
|
56
|
+
setting record using `account.settings.version_record`
|
51
57
|
|
52
58
|
### Bulk operations
|
53
59
|
|
54
60
|
Stored properties can also be updated with the update_attributes and update_attributes! methods by
|
55
61
|
enabling nested attributes. Like this (from the test cases):
|
56
62
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
63
|
+
```ruby
|
64
|
+
@account.texts_attributes = [
|
65
|
+
{ :name => "foo", :value => "1" },
|
66
|
+
{ :name => "bar", :value => "0" }
|
67
|
+
]
|
68
|
+
```
|
61
69
|
|
62
70
|
And for existing records:
|
63
71
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
72
|
+
```ruby
|
73
|
+
@account.update_attributes!(:texts_attributes => [
|
74
|
+
{ :id => @account.texts.foo.id, :name => "foo", :value => "0" },
|
75
|
+
{ :id => @account.texts.bar.id, :name => "bar", :value => "1" }
|
76
|
+
])
|
77
|
+
```
|
68
78
|
|
69
79
|
Using nested attributes is subject to implementing your own security measures for mass update assignments.
|
70
80
|
Alternatively, it is possible to use a custom hash structure:
|
71
81
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
}
|
76
|
-
|
82
|
+
```ruby
|
83
|
+
params = {
|
84
|
+
:settings => { :version => "v4.0", :featured => "1" },
|
85
|
+
:texts => { :epilogue => "Wibble wobble" }
|
86
|
+
}
|
77
87
|
|
78
|
-
|
88
|
+
@account.update_attributes(params)
|
89
|
+
```
|
90
|
+
|
91
|
+
The above will not update `featured` as this has the protected flag set and is hence protected from
|
79
92
|
mass updates.
|
80
93
|
|
81
94
|
### View helpers
|
82
95
|
|
83
96
|
We support a couple of convenience mechanisms for building forms and putting the values into the above hash structure. So far, only support check boxes and radio buttons:
|
84
97
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
98
|
+
```erb
|
99
|
+
<% form_for(:account, :html => { :method => :put }) do |f| %>
|
100
|
+
<h3><%= f.property_set(:settings).check_box :activated %> Activated?</h3>
|
101
|
+
<h3><%= f.property_set(:settings).radio_button :hot, "yes" %> Hot</h3>
|
102
|
+
<h3><%= f.property_set(:settings).radio_button :not, "no" %> Not</h3>
|
103
|
+
<h3><%= f.property_set(:settings).select :level, [["One", 1], ["Two", 2]] %></h3>
|
104
|
+
<% end %>
|
105
|
+
```
|
91
106
|
|
92
107
|
## Installation
|
93
108
|
|
94
109
|
Install the gem in your rails project by putting it in your Gemfile:
|
95
110
|
|
96
|
-
|
111
|
+
```
|
112
|
+
gem "property_sets"
|
113
|
+
```
|
97
114
|
|
98
115
|
Also remember to create the storage table(s), if for example you are going to be using this with an accounts model and a "settings" property set, you can define the table like:
|
99
116
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
117
|
+
```ruby
|
118
|
+
create_table :account_settings do |t|
|
119
|
+
t.integer :account_id, :null => false
|
120
|
+
t.string :name, :null => false
|
121
|
+
t.string :value
|
122
|
+
t.timestamps
|
123
|
+
end
|
106
124
|
|
107
|
-
|
125
|
+
add_index :account_settings, [ :account_id, :name ], :unique => true
|
126
|
+
```
|
108
127
|
|
109
128
|
## Requirements
|
110
129
|
|
111
130
|
* ActiveRecord
|
112
131
|
* ActiveSupport
|
113
132
|
|
114
|
-
## License
|
133
|
+
## License and copyright
|
115
134
|
|
116
135
|
Copyright 2013 Zendesk
|
117
136
|
|
@@ -4,14 +4,13 @@ module ActionView
|
|
4
4
|
module Helpers
|
5
5
|
class FormBuilder
|
6
6
|
class PropertySetFormBuilderProxy
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
self.object_name = object_name
|
7
|
+
attr_reader :property_set, :template, :object_name, :object
|
8
|
+
|
9
|
+
def initialize(property_set, template, object_name, object)
|
10
|
+
@property_set = property_set
|
11
|
+
@template = template
|
12
|
+
@object_name = object_name
|
13
|
+
@object = object
|
15
14
|
end
|
16
15
|
|
17
16
|
def check_box(property, options = {}, checked_value = "1", unchecked_value = "0")
|
@@ -46,29 +45,33 @@ module ActionView
|
|
46
45
|
template.select("#{object_name}[#{property_set}]", property, choices, { :selected => current_value }, html_options )
|
47
46
|
end
|
48
47
|
|
48
|
+
private
|
49
|
+
|
49
50
|
def prepare_id_name(property, options)
|
50
51
|
throw "Invalid options type #{options.inspect}" unless options.is_a?(Hash)
|
51
52
|
|
52
53
|
options.clone.tap do |prepared_options|
|
53
|
-
|
54
|
-
|
55
|
-
throw "No @#{object_name} in scope" if instance.nil?
|
56
|
-
throw "The property_set_check_box only works on models with property set #{property_set}" unless instance.respond_to?(property_set)
|
57
|
-
|
54
|
+
prepared_options[:object] = object || fetch_target_object
|
58
55
|
prepared_options[:id] ||= "#{object_name}_#{property_set}_#{property}"
|
59
56
|
prepared_options[:name] = "#{object_name}[#{property_set}][#{property}]"
|
60
|
-
prepared_options[:object] = instance
|
61
57
|
end
|
62
58
|
end
|
63
59
|
|
60
|
+
def fetch_target_object
|
61
|
+
instance = template.instance_variable_get("@#{object_name}")
|
62
|
+
|
63
|
+
throw "No @#{object_name} in scope" if instance.nil?
|
64
|
+
throw "The property_set_check_box only works on models with property set #{property_set}" unless instance.respond_to?(property_set)
|
65
|
+
|
66
|
+
instance
|
67
|
+
end
|
68
|
+
|
64
69
|
def prepare_options(property, options, &block)
|
65
70
|
options = prepare_id_name(property, options)
|
66
71
|
options[:checked] = yield(options[:object].send(property_set))
|
67
72
|
options
|
68
73
|
end
|
69
74
|
|
70
|
-
private
|
71
|
-
|
72
75
|
def cast_boolean(value)
|
73
76
|
case value
|
74
77
|
when TrueClass then '1'
|
@@ -80,7 +83,7 @@ module ActionView
|
|
80
83
|
end
|
81
84
|
|
82
85
|
def property_set(identifier)
|
83
|
-
PropertySetFormBuilderProxy.new(identifier, @template,
|
86
|
+
PropertySetFormBuilderProxy.new(identifier, @template, object_name, object)
|
84
87
|
end
|
85
88
|
|
86
89
|
end
|
data/property_sets.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Gem::Specification.new "property_sets", "1.0.
|
1
|
+
Gem::Specification.new "property_sets", "1.0.5" do |s|
|
2
2
|
s.summary = "Property sets for ActiveRecord."
|
3
3
|
s.description = "This gem is an ActiveRecord extension which provides a convenient interface for managing per row properties."
|
4
4
|
s.authors = ["Morten Primdahl"]
|
@@ -11,8 +11,6 @@ class TestViewExtensions < ActiveSupport::TestCase
|
|
11
11
|
@template = stub
|
12
12
|
@builder = ActionView::Helpers::FormBuilder.new(@object_name, @object, @template, {}, 'proc')
|
13
13
|
@proxy = @builder.property_set(@property_set)
|
14
|
-
|
15
|
-
@template.stubs(:instance_variable_get).with("@#{@object_name}").returns(@object)
|
16
14
|
end
|
17
15
|
|
18
16
|
should "provide a form builder proxy" do
|
@@ -20,6 +18,17 @@ class TestViewExtensions < ActiveSupport::TestCase
|
|
20
18
|
assert_equal @property_set, @proxy.property_set
|
21
19
|
end
|
22
20
|
|
21
|
+
should "fetch the target object when not available" do
|
22
|
+
@builder = ActionView::Helpers::FormBuilder.new(@object_name, nil, @template, {}, 'proc')
|
23
|
+
@proxy = @builder.property_set(@property_set)
|
24
|
+
|
25
|
+
@object.stubs(@property_set).returns(stub(@property => 'value'))
|
26
|
+
@template.stubs(:hidden_field)
|
27
|
+
|
28
|
+
@template.expects(:instance_variable_get).with("@#{@object_name}").returns(@object)
|
29
|
+
@proxy.hidden_field(@property)
|
30
|
+
end
|
31
|
+
|
23
32
|
context "#check_box" do
|
24
33
|
context "when called with checked true for a truth value" do
|
25
34
|
setup do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: property_sets
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
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: 2013-04-
|
12
|
+
date: 2013-04-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|