property_sets 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,18 +1,16 @@
1
1
  = Property sets
2
2
 
3
- The property_set gem is an evolution of the has_settings gem which was an evolution of the features gem. This is getting old.
4
-
5
3
  This gem is a way for you to use a basic "key/value" store for storing attributes for a given model in a relational fashion where there's a row per attribute. Alternatively you'd need to add a new column per attribute to your main table, or serialize the attributes and their values.
6
4
 
7
5
  == Description
8
6
 
9
- You configure the allowed stored properties by specifying these in an initializer:
7
+ You configure the allowed stored properties by specifying these in the model:
10
8
 
11
9
  class Account < ActiveRecord::Base
12
10
  property_set :settings do
13
11
  property :version, :default => "v1.0"
14
- property :featured
15
- property :product
12
+ property :featured, :protected => true
13
+ property :activated
16
14
  end
17
15
 
18
16
  property_set :texts do
@@ -34,25 +32,68 @@ The declared properties can then be accessed runtime via the defined association
34
32
  # Destroy the version record
35
33
  account.settings.version.destroy
36
34
 
37
- On top of the basic access paths, there are some short cuts:
35
+ === Convenience methods
36
+
37
+ On top of the basic access paths, there are some short cuts, mainly convenience methods for dealing with booleans:
38
+
39
+ # immediately changes the value of the setting
40
+ account.settings.version=("v3.0")
41
+
42
+ # coerces the setting to boolean AR style
43
+ account.settings.featured?
38
44
 
39
- account.settings.featured=(1) # immediately changes the value of the setting
40
- account.settings.featured? # coerces the setting to boolean AR style
41
- account.settings.featured.enable # sets the value of this setting to a true value
42
- account.settings.featured.disable # sets the value of this setting to a false value
45
+ # sets the value of this setting to a true value
46
+ account.settings.featured.enable
43
47
 
44
- If the value has never been set, a nil (or default) is returned. And that's pretty much it.
48
+ # sets the value of this setting to a false value
49
+ account.settings.featured.disable
50
+
51
+ === Bulk operations
45
52
 
46
53
  Stored properties can also be updated with the update_attributes and update_attributes! methods by
47
- enabling nested attributes. See the test cases for examples.
54
+ enabling nested attributes. Like this (from the test cases):
55
+
56
+ @account.texts_attributes = [
57
+ { :name => "foo", :value => "1" },
58
+ { :name => "bar", :value => "0" }
59
+ ]
60
+
61
+ And for existing records:
62
+
63
+ @account.update_attributes!(:texts_attributes => [
64
+ { :id => @account.texts.foo.id, :name => "foo", :value => "0" },
65
+ { :id => @account.texts.bar.id, :name => "bar", :value => "1" }
66
+ ])
67
+
68
+ Using nested attributes is subject to implementing your own security measures for mass update assignments.
69
+ Alternatively, it is possible to use a custom hash structure:
70
+
71
+ params = { :property_sets => {
72
+ :settings => { :version => "v4.0", :featured => "1" },
73
+ :texts => { :epilogue => "Wibble wobble" }
74
+ }}
75
+ @account.update_attributes(params)
76
+
77
+ The above will not update +featured+ as this has the protected flag set and is hence protected from
78
+ mass updates.
79
+
80
+ === View helpers
81
+
82
+ We support a single convenience mechanism for building forms and putting the values into the above hash structure. So far, we only support check boxes:
83
+
84
+ <% form_for(:account, :html => { :method => :put }) do |f| %>
85
+ <h3>
86
+ <%= f.property_set(:settings).check_box :activated %> Activated?
87
+ </h3>
88
+ <% end %>
48
89
 
49
90
  == Installation
50
91
 
51
92
  Install the gem in your rails project by putting it in your Gemfile:
52
93
 
53
- gem 'property_set'
94
+ gem "property_sets"
54
95
 
55
- Also remember to create the storage table, if for example you are going to be using this with an accounts model, you can define the table like:
96
+ 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:
56
97
 
57
98
  create_table :account_settings do |t|
58
99
  t.integer :account_id, :null => false
@@ -66,13 +107,13 @@ Also remember to create the storage table, if for example you are going to be us
66
107
  == Requirements
67
108
 
68
109
  * ActiveRecord
69
- * ActionPack
110
+ * ActiveSupport
70
111
 
71
112
  == LICENSE:
72
113
 
73
114
  (The MIT License)
74
115
 
75
- Copyright (c) 2010 Zendesk
116
+ Copyright (c) 2011 Zendesk
76
117
 
77
118
  Permission is hereby granted, free of charge, to any person
78
119
  obtaining a copy of this software and associated documentation
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.11
1
+ 0.0.12
data/lib/property_sets.rb CHANGED
@@ -1,6 +1,6 @@
1
+ require 'property_sets/property_set_helper'
1
2
  require 'property_sets/property_set_model'
2
3
  require 'property_sets/active_record_extension'
3
- require 'property_sets/property_set_helper'
4
4
 
5
5
  module PropertySets
6
6
  def self.ensure_property_set_class(association, owner_class)
@@ -34,10 +34,15 @@ module PropertySets
34
34
  has_many association.to_s.pluralize.to_sym, :class_name => property_class.name, :dependent => :destroy do
35
35
 
36
36
  # Accepts a name value pair hash { :name => 'value', :pairs => true } and builds a property for each key
37
- def bulk(property_pairs)
37
+ def bulk(property_pairs, with_protection = false)
38
38
  property_pairs.keys.each do |name|
39
- value = property_pairs[name]
40
- self << proxy_reflection.klass.new(:name => name.to_s, :value => value)
39
+ record = lookup(name).record
40
+ if with_protection && record.protected?
41
+ logger.warn("Someone tried to update the protected #{name} property to #{property_pairs[name]}")
42
+ else
43
+ record.value = property_pairs[name]
44
+ self << record
45
+ end
41
46
  end
42
47
  end
43
48
 
@@ -73,9 +78,32 @@ module PropertySets
73
78
  end
74
79
  end
75
80
 
81
+ module InstanceMethods
82
+ def update_attributes_with_property_sets(attributes)
83
+ update_property_set_attributes(attributes)
84
+ update_attributes_without_property_sets(attributes)
85
+ end
86
+
87
+ def update_attributes_with_property_sets!(attributes)
88
+ update_property_set_attributes(attributes)
89
+ update_attributes_without_property_sets!(attributes)
90
+ end
91
+
92
+ def update_property_set_attributes(attributes)
93
+ if attributes && property_sets = attributes.delete(:property_sets)
94
+ property_sets.each do |property_set, property_set_attributes|
95
+ send(property_set).bulk(property_set_attributes, true)
96
+ end
97
+ end
98
+ end
99
+ end
76
100
  end
77
101
  end
78
102
 
79
103
  ActiveRecord::Base.class_eval do
80
- extend PropertySets::ActiveRecordExtension::ClassMethods
104
+ include PropertySets::ActiveRecordExtension::InstanceMethods
105
+ extend PropertySets::ActiveRecordExtension::ClassMethods
106
+
107
+ alias_method_chain :update_attributes, :property_sets
108
+ alias_method_chain :update_attributes!, :property_sets
81
109
  end
@@ -0,0 +1,22 @@
1
+ require 'delegate'
2
+
3
+ module PropertySets
4
+ class FormBuilderProxy < Delegator
5
+ attr_accessor :builder
6
+ attr_accessor :property_set
7
+
8
+ def initialize(property_set, builder)
9
+ self.property_set = property_set
10
+ self.builder = builder
11
+ end
12
+
13
+ def __getobj__
14
+ builder
15
+ end
16
+
17
+ def check_box(property, options = {}, checked_value = "1", unchecked_value = "0")
18
+ builder.property_set_check_box(property_set, property, options, checked_value, unchecked_value)
19
+ end
20
+ end
21
+ end
22
+
@@ -1,19 +1,28 @@
1
+ require 'property_sets/form_builder_proxy'
2
+
1
3
  module ActionView
2
4
  module Helpers
3
- def setting_check_box(model_name, method, options = {}, checked_value = "1", unchecked_value = "0")
5
+ # property_set_check_box(:account, :property_association, :property_key, options)
6
+ def property_set_check_box(model_name, property_set, property, options = {}, checked_value = "1", unchecked_value = "0")
4
7
  the_model = @template.instance_variable_get("@#{model_name}")
8
+
5
9
  throw "No @#{model_name} in scope" if the_model.nil?
6
- throw "The setting_check_box only works on models with settings" unless the_model.respond_to?(:settings)
7
- options[:checked] = the_model.settings.send("#{method}?")
8
- options[:id] ||= "#{model_name}_settings_#{method}"
9
- options[:name] = "#{model_name}[settings][#{method}]"
10
- @template.check_box(model_name, "settings_#{method}", options, checked_value, unchecked_value)
10
+ throw "The property_set_check_box only works on models with property set #{property_set}" unless the_model.respond_to?(property_set)
11
+
12
+ options[:checked] = the_model.send(property).send("#{method}?")
13
+ options[:id] ||= "#{model_name}_property_sets_#{property_set}_#{method}"
14
+ options[:name] = "#{model_name}[property_sets][#{property_set}][#{method}]"
15
+ @template.check_box(model_name, "property_sets_#{property_set}_#{method}", options, checked_value, unchecked_value)
16
+ end
17
+ end
18
+
19
+ class FormBuilder
20
+ def property_set(identifier)
21
+ PropertySets::FormBuilderProxy.new(identifier, self)
11
22
  end
12
23
 
13
- class FormBuilder
14
- def setting_check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
15
- @template.setting_check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
16
- end
24
+ def property_set_check_box(property_set, property, options, checked_value, unchecked_value)
25
+ @template.property_set_check_box(@object_name, property_set, property, objectify_options(options), checked_value, unchecked_value)
17
26
  end
18
27
  end
19
28
  end
@@ -20,6 +20,10 @@ module PropertySets
20
20
  self
21
21
  end
22
22
 
23
+ def protected?
24
+ self.class.protected?(name.to_sym)
25
+ end
26
+
23
27
  def to_s
24
28
  value.to_s
25
29
  end
@@ -43,7 +47,9 @@ module PropertySets
43
47
  end
44
48
 
45
49
  def update_owner_timestamp
46
- owner_class_instance.update_attribute(:updated_at, Time.now) if owner_class_instance && !owner_class_instance.new_record?
50
+ if owner_class_instance && !owner_class_instance.new_record? && owner_class_instance.updated_at < 1.second.ago
51
+ owner_class_instance.update_attribute(:updated_at, Time.now)
52
+ end
47
53
  end
48
54
 
49
55
  def reset_owner_association
@@ -72,6 +78,10 @@ module PropertySets
72
78
  @properties[key] && @properties[key].key?(:default) ? @properties[key][:default] : nil
73
79
  end
74
80
 
81
+ def protected?(key)
82
+ @properties[key] && !!@properties[key][:protected]
83
+ end
84
+
75
85
  def owner_class=(owner_class)
76
86
  @owner_class_sym = owner_class.name.underscore.to_sym
77
87
  belongs_to owner_class_sym
@@ -5,28 +5,27 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{property_sets}
8
- s.version = "0.0.11"
8
+ s.version = "0.0.12"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Morten Primdahl"]
12
- s.date = %q{2011-01-13}
12
+ s.date = %q{2011-01-17}
13
13
  s.description = %q{This gem is an ActiveRecord extension which provides a convenient interface for managing per row properties}
14
14
  s.email = %q{morten@zendesk.com}
15
15
  s.extra_rdoc_files = [
16
- "LICENSE",
17
16
  "LICENSE.txt",
18
17
  "README.rdoc"
19
18
  ]
20
19
  s.files = [
21
20
  ".document",
22
21
  "Gemfile",
23
- "LICENSE",
24
22
  "LICENSE.txt",
25
23
  "README.rdoc",
26
24
  "Rakefile",
27
25
  "VERSION",
28
26
  "lib/property_sets.rb",
29
27
  "lib/property_sets/active_record_extension.rb",
28
+ "lib/property_sets/form_builder_proxy.rb",
30
29
  "lib/property_sets/property_set_helper.rb",
31
30
  "lib/property_sets/property_set_model.rb",
32
31
  "property_sets.gemspec",
@@ -7,6 +7,7 @@ class Account < ActiveRecord::Base
7
7
  property :baz
8
8
  property :hep, :default => 'skep'
9
9
  property :bob
10
+ property :bla, :protected => true
10
11
  end
11
12
 
12
13
  property_set :texts do
@@ -31,6 +32,10 @@ class TestPropertySets < ActiveSupport::TestCase
31
32
  assert defined?(AccountText)
32
33
  end
33
34
 
35
+ should "support protecting attributes" do
36
+ assert @account.settings.bla.protected?
37
+ end
38
+
34
39
  should "be empty on a new account" do
35
40
  assert @account.settings.empty?
36
41
  assert @account.texts.empty?
@@ -114,40 +119,99 @@ class TestPropertySets < ActiveSupport::TestCase
114
119
  assert @account.settings.foo.value.nil?
115
120
  end
116
121
 
117
- should "support bulk build multiple properties in one go" do
118
- @account.settings.bulk(:foo => "123", :bar => "456")
119
- @account.save!
120
- assert_equal @account.reload.settings.size, 2
121
- assert_equal @account.settings.foo.value, "123"
122
- assert_equal @account.settings.foo.name, "foo"
123
- assert_equal @account.settings.bar.value, "456"
124
- assert_equal @account.settings.bar.name, "bar"
125
- end
126
-
127
- should "be updatable as nested attributes" do
128
- assert !@account.texts.foo?
129
- assert !@account.texts.bar?
130
- assert !@account.texts.foo.id
131
- assert !@account.texts.bar.id
132
- assert @account.texts.empty?
122
+ context "bulk updates" do
123
+ should "support bulk create/update of multiple properties in one go" do
124
+ [ @account, Account.new(:name => "Mibble") ].each do |account|
125
+ account.settings.bulk(:foo => "123", :bar => "456")
126
+ account.save!
127
+
128
+ assert_equal account.reload.settings.size, 2
129
+ assert_equal account.settings.foo.value, "123"
130
+ assert_equal account.settings.foo.name, "foo"
131
+ assert_equal account.settings.bar.value, "456"
132
+ assert_equal account.settings.bar.name, "bar"
133
+
134
+ account.settings.bulk(:bar => "789", :baz => "012")
135
+ account.save!
136
+
137
+ assert_equal account.reload.settings.size, 3
138
+ assert_equal account.settings.foo.value, "123"
139
+ assert_equal account.settings.bar.value, "789"
140
+ assert_equal account.settings.baz.value, "012"
141
+ end
142
+ end
133
143
 
134
- assert @account.texts_attributes = [{ :name => "foo", :value => "1" }, { :name => "bar", :value => "0" }]
135
- @account.save!
144
+ should "be updateable as AR nested attributes" do
145
+ assert !@account.texts.foo?
146
+ assert !@account.texts.bar?
147
+ assert !@account.texts.foo.id
148
+ assert !@account.texts.bar.id
149
+ assert @account.texts.empty?
150
+
151
+ assert @account.texts_attributes = [{ :name => "foo", :value => "1" }, { :name => "bar", :value => "0" }]
152
+ @account.save!
153
+
154
+ assert @account.texts.foo?
155
+ assert !@account.texts.bar?
156
+ assert @account.texts.foo.id
157
+ assert @account.texts.bar.id
158
+
159
+ @account.update_attributes!(:texts_attributes => [
160
+ { :id => @account.texts.foo.id, :name => "foo", :value => "0" },
161
+ { :id => @account.texts.bar.id, :name => "bar", :value => "1" }
162
+ ])
163
+ assert !@account.texts.foo?
164
+ assert @account.texts.bar?
165
+ end
136
166
 
137
- assert @account.texts.foo?
138
- assert !@account.texts.bar?
139
- assert @account.texts.foo.id
140
- assert @account.texts.bar.id
167
+ should "be updateable as a nested structure" do
168
+ assert !@account.settings.foo?
169
+ assert !@account.settings.bar?
170
+ assert !@account.settings.foo.id
171
+ assert !@account.settings.bar.id
172
+ assert @account.settings.empty?
173
+
174
+ attribs = {
175
+ :name => "Kim",
176
+ :property_sets => {
177
+ :settings => { :foo => "1", :bar => "0" }
178
+ }
179
+ }
180
+
181
+ assert @account.update_attributes(attribs)
182
+ @account.save!
183
+
184
+ assert @account.settings.foo?
185
+ assert !@account.settings.bar?
186
+ assert @account.settings.foo.id
187
+ assert @account.settings.bar.id
188
+ assert @account.settings.foo.value == "1"
189
+ assert @account.settings.bar.value == "0"
190
+
191
+ attribs = {
192
+ :name => "Kim",
193
+ :property_sets => {
194
+ :settings => { :foo => "1", :bar => "1", :baz => "1", :bla => "1" }
195
+ }
196
+ }
197
+
198
+ assert @account.update_attributes!(attribs)
199
+
200
+ assert @account.settings.foo?
201
+ assert @account.settings.bar?
202
+ assert @account.settings.baz?
203
+ assert !@account.settings.bla?
204
+ end
205
+ end
141
206
 
142
- @account.update_attributes!(:texts_attributes => [
143
- { :id => @account.texts.foo.id, :name => "foo", :value => "0" },
144
- { :id => @account.texts.bar.id, :name => "bar", :value => "1" }
145
- ])
146
- assert !@account.texts.foo?
147
- assert @account.texts.bar?
207
+ context "view construction" do
208
+ should "provide a form builder proxy" do
209
+ proxy = ActionView::FormBuilder.new.property_set(:foo)
210
+ assert proxy.is_a?(PropertySets::FormBuilderProxy)
211
+ assert_equal :foo, proxy.property_set
212
+ proxy.builder.expects(:property_set_check_box).once.with(:foo, :bar, {}, "1", "0")
213
+ proxy.check_box(:bar)
214
+ end
148
215
  end
149
216
  end
150
217
  end
151
-
152
-
153
-
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: property_sets
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 11
10
- version: 0.0.11
9
+ - 12
10
+ version: 0.0.12
11
11
  platform: ruby
12
12
  authors:
13
13
  - Morten Primdahl
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-13 00:00:00 -08:00
18
+ date: 2011-01-17 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -99,19 +99,18 @@ executables: []
99
99
  extensions: []
100
100
 
101
101
  extra_rdoc_files:
102
- - LICENSE
103
102
  - LICENSE.txt
104
103
  - README.rdoc
105
104
  files:
106
105
  - .document
107
106
  - Gemfile
108
- - LICENSE
109
107
  - LICENSE.txt
110
108
  - README.rdoc
111
109
  - Rakefile
112
110
  - VERSION
113
111
  - lib/property_sets.rb
114
112
  - lib/property_sets/active_record_extension.rb
113
+ - lib/property_sets/form_builder_proxy.rb
115
114
  - lib/property_sets/property_set_helper.rb
116
115
  - lib/property_sets/property_set_model.rb
117
116
  - property_sets.gemspec
data/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- Copyright (c) 2009 Morten Primdahl
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.