active_attr 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of active_attr might be problematic. Click here for more details.

@@ -0,0 +1,42 @@
1
+ require "active_attr/mass_assignment"
2
+ require "active_support/concern"
3
+ require "active_model"
4
+
5
+ module ActiveAttr
6
+ # MassAssignmentSecurity allows you to bulk set and update a blacklist or
7
+ # whitelist of attributes
8
+ #
9
+ # Including MassAssignmentSecurity extends all {ActiveAttr::MassAssignment}
10
+ # methods to honor any declared attribute permissions.
11
+ #
12
+ # @example Usage
13
+ # class Person
14
+ # include ActiveAttr::MassAssignmentSecurity
15
+ # end
16
+ #
17
+ # @since 0.3.0
18
+ module MassAssignmentSecurity
19
+ extend ActiveSupport::Concern
20
+ include MassAssignment
21
+ include ActiveModel::MassAssignmentSecurity
22
+
23
+ # Mass update a model's attributes, honoring attribute permissions
24
+ #
25
+ # @param (see MassAssignment#assign_attributes)
26
+ # @param [Hash, #[]] options Options that affect mass assignment
27
+ #
28
+ # @option options [Symbol] :as (:default) Mass assignment role
29
+ # @option options [true, false] :without_protection (false) Bypass mass
30
+ # assignment security if true
31
+ #
32
+ # @since 0.3.0
33
+ def assign_attributes(new_attributes, options={})
34
+ if new_attributes && !options[:without_protection]
35
+ mass_assignment_role = options[:as] || :default
36
+ new_attributes = sanitize_for_mass_assignment new_attributes, mass_assignment_role
37
+ end
38
+
39
+ super
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,62 @@
1
+ require "active_attr/attributes"
2
+ require "active_attr/unknown_attribute_error"
3
+ require "active_support/concern"
4
+ require "active_support/core_ext/object/blank"
5
+
6
+ module ActiveAttr
7
+ # QueryAttributes provides instance methods for querying attributes.
8
+ #
9
+ # @example Usage
10
+ # class Person
11
+ # include ActiveAttr::QueryAttributes
12
+ # attribute :name
13
+ # end
14
+ #
15
+ # person = Person.new
16
+ # person.name? #=> false
17
+ # person.name = "Chris Griego"
18
+ # person.name? #=> true
19
+ #
20
+ # @since 0.3.0
21
+ module QueryAttributes
22
+ extend ActiveSupport::Concern
23
+ include Attributes
24
+
25
+ included do
26
+ attribute_method_suffix "?"
27
+ end
28
+
29
+ # Test the presence of an attribute
30
+ #
31
+ # Similar to an ActiveRecord model, when the attribute is a zero value or
32
+ # is a string that represents false, the method returns false.
33
+ #
34
+ # @example Query an attribute
35
+ # person.query_attribute(:name)
36
+ #
37
+ # @param [String, Symbol, #to_s] name The name of the attribute to query
38
+ #
39
+ # @return [true, false] The presence of the attribute
40
+ #
41
+ # @raise [UnknownAttributeError] if the attribute is unknown
42
+ #
43
+ # @since 0.3.0
44
+ def query_attribute(name)
45
+ if respond_to? "#{name}?"
46
+ send "#{name}?"
47
+ else
48
+ raise UnknownAttributeError, "unknown attribute: #{name}"
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def attribute?(name)
55
+ case value = read_attribute(name)
56
+ when "false", "FALSE", "f", "F" then false
57
+ when Numeric, /^\-?[0-9]/ then !value.to_f.zero?
58
+ else value.present?
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,10 @@
1
+ require "active_attr/logger"
2
+
3
+ module ActiveAttr
4
+ # @private
5
+ class Railtie < Rails::Railtie
6
+ initializer "active_attr.logger" do
7
+ Logger.logger ||= ::Rails.logger
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ require "active_attr/error"
2
+
3
+ module ActiveAttr
4
+ # This exception is raised if attempting to assign unknown attributes when
5
+ # using {Attributes}
6
+ #
7
+ # @example Rescuing an UnknownAttributeError error
8
+ # begin
9
+ # person.write_attribute(:middle_initial, "J")
10
+ # rescue ActiveAttr::UnknownAttributeError
11
+ # logger.error "attempted to write an unknown attribute"
12
+ # raise
13
+ # end
14
+ #
15
+ # @since 0.3.0
16
+ class UnknownAttributeError < NoMethodError
17
+ include Error
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveAttr
2
2
  # Complete version string
3
3
  # @since 0.1.0
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -1,6 +1,7 @@
1
1
  require "spec_helper"
2
2
  require "active_attr/attributes"
3
3
  require "active_model"
4
+ require "factory_girl"
4
5
 
5
6
  module ActiveAttr
6
7
  describe Attributes do
@@ -107,5 +108,108 @@ module ActiveAttr
107
108
  include_examples "serialization method"
108
109
  end
109
110
  end
111
+
112
+ context "building with FactoryGirl" do
113
+ subject { FactoryGirl.build(:person) }
114
+
115
+ before do
116
+ Object.const_set("Person", model_class)
117
+
118
+ Factory.define :person, :class => :person do |model|
119
+ model.first_name "Chris"
120
+ model.last_name "Griego"
121
+ end
122
+ end
123
+
124
+ after do
125
+ FactoryGirl.factories.clear
126
+ Object.send :remove_const, "Person"
127
+ end
128
+
129
+ let :model_class do
130
+ Class.new do
131
+ include Attributes
132
+ attribute :first_name
133
+ attribute :last_name
134
+ end
135
+ end
136
+
137
+ it "builds an instance of the model class" do
138
+ should be_a_kind_of Person
139
+ end
140
+
141
+ it "sets the attributes" do
142
+ subject.first_name.should == "Chris"
143
+ subject.last_name.should == "Griego"
144
+ end
145
+ end
146
+
147
+ context "defining dangerous attributes" do
148
+ shared_examples "defining a dangerous attribute" do
149
+ it "defining an attribute that conflicts with #{described_class} raises DangerousAttributeError" do
150
+ expect { model_class.attribute(:write_attribute) }.to raise_error DangerousAttributeError, %{an attribute method named "write_attribute" would conflict with an existing method}
151
+ end
152
+
153
+ it "defining an attribute that conflicts with ActiveModel::AttributeMethods raises DangerousAttributeError" do
154
+ expect { model_class.attribute(:attribute_method_matchers) }.to raise_error DangerousAttributeError, %{an attribute method named "attribute_method_matchers" would conflict with an existing method}
155
+ end
156
+
157
+ it "defining an :id attribute does not raise" do
158
+ expect { model_class.attribute(:id) }.not_to raise_error
159
+ end
160
+
161
+ it "defining a :type attribute does not raise" do
162
+ expect { model_class.attribute(:type) }.not_to raise_error
163
+ end
164
+
165
+ it "defining an attribute that conflicts with Kernel raises DangerousAttributeError" do
166
+ expect { model_class.attribute(:puts) }.to raise_error DangerousAttributeError
167
+ end
168
+
169
+ it "defining an attribute that conflicts with Object raises DangerousAttributeError" do
170
+ expect { model_class.attribute(:class) }.to raise_error DangerousAttributeError
171
+ end
172
+
173
+ it "defining an attribute that conflicts with BasicObject raises DangerousAttributeError" do
174
+ expect { model_class.attribute(:instance_eval) }.to raise_error DangerousAttributeError
175
+ end
176
+
177
+ it "defining an attribute that conflicts with a properly implemented method_missing callback raises DangerousAttributeError" do
178
+ expect { model_class.attribute(:my_proper_missing_method) }.to raise_error DangerousAttributeError
179
+ end
180
+
181
+ it "defining an attribute that conflicts with a less properly implemented method_missing callback raises DangerousAttributeError" do
182
+ expect { model_class.attribute(:my_less_proper_missing_method) }.to raise_error DangerousAttributeError
183
+ end
184
+ end
185
+
186
+ let :dangerous_model_class do
187
+ Class.new do
188
+ include Attributes
189
+
190
+ def method_missing(method_name, *)
191
+ super if %w(my_proper_missing_method my_less_proper_missing_method).include? method_name.to_s
192
+ end
193
+
194
+ def respond_to_missing?(method_name, *)
195
+ method_name.to_s == "my_proper_missing_method" || super
196
+ end
197
+
198
+ def respond_to?(method_name, include_private=false)
199
+ super || method_name.to_s == "my_less_proper_missing_method" || (RUBY_VERSION < "1.9" && respond_to_missing?(method_name, include_private))
200
+ end
201
+ end
202
+ end
203
+
204
+ context "on a model class" do
205
+ let(:model_class) { dangerous_model_class }
206
+ include_examples "defining a dangerous attribute"
207
+ end
208
+
209
+ context "on a child class" do
210
+ let(:model_class) { Class.new(dangerous_model_class) }
211
+ include_examples "defining a dangerous attribute"
212
+ end
213
+ end
110
214
  end
111
215
  end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+ require "active_attr/query_attributes"
3
+
4
+ module ActiveAttr
5
+ describe QueryAttributes do
6
+ context "defining dangerous attributes" do
7
+ shared_examples "defining a dangerous queryable attribute" do
8
+ it "defining an attribute that conflicts with ActiveModel::AttributeMethods raises DangerousAttributeError" do
9
+ expect { model_class.attribute(:attribute_method) }.to raise_error DangerousAttributeError, %{an attribute method named "attribute_method?" would conflict with an existing method}
10
+ end
11
+
12
+ it "defining an attribute that conflicts with Kernel raises DangerousAttributeError" do
13
+ expect { model_class.attribute(:block_given) }.to raise_error DangerousAttributeError
14
+ end
15
+
16
+ it "defining an attribute that conflicts with Object raises DangerousAttributeError" do
17
+ expect { model_class.attribute(:nil) }.to raise_error DangerousAttributeError
18
+ end
19
+ end
20
+
21
+ context "on a model class" do
22
+ let(:model_class) { Class.new { include QueryAttributes } }
23
+ include_examples "defining a dangerous queryable attribute"
24
+ end
25
+
26
+ context "on a child class" do
27
+ let(:model_class) { Class.new(Class.new { include QueryAttributes }) }
28
+ include_examples "defining a dangerous queryable attribute"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -46,6 +46,11 @@ shared_examples "#assign_attribures", :assign_attributes => true do
46
46
  person
47
47
  end
48
48
 
49
+ def mass_assign_attributes_with_options(attributes, options)
50
+ person.assign_attributes attributes, options
51
+ person
52
+ end
53
+
49
54
  it "raises ArgumentError when called with three arguments" do
50
55
  expect { subject.new.assign_attributes({}, {}, nil) }.to raise_error ArgumentError
51
56
  end
@@ -79,6 +84,10 @@ shared_examples "#initialize", :initialize => true do
79
84
  subject.new(attributes)
80
85
  end
81
86
 
87
+ def mass_assign_attributes_with_options(attributes, options)
88
+ subject.new(attributes, options)
89
+ end
90
+
82
91
  it "invokes the superclass initializer" do
83
92
  subject.new.should be_initialized
84
93
  end
@@ -4,12 +4,14 @@ require "active_attr/attributes"
4
4
  module ActiveAttr
5
5
  describe Attributes do
6
6
  subject { model_class.new }
7
+ let(:last_name) { "Poweski" }
7
8
 
8
9
  let :model_class do
9
10
  Class.new do
10
11
  include InitializationVerifier
11
12
  include Attributes
12
- attribute :name
13
+ attribute :first_name
14
+ attribute :last_name
13
15
  attribute :amount
14
16
 
15
17
  def self.name
@@ -24,9 +26,17 @@ module ActiveAttr
24
26
  super
25
27
  end
26
28
 
27
- def initialize(name=nil)
29
+ def last_name=(value)
30
+ super(value.to_s.upcase)
31
+ end
32
+
33
+ def last_name
34
+ super || "Poweski"
35
+ end
36
+
37
+ def initialize(first_name=nil)
28
38
  super
29
- write_attribute(:name, name)
39
+ write_attribute(:first_name, first_name)
30
40
  end
31
41
  end
32
42
  end
@@ -43,7 +53,7 @@ module ActiveAttr
43
53
 
44
54
  describe ".attribute" do
45
55
  it "creates an attribute with no options" do
46
- model_class.attributes.should include(AttributeDefinition.new(:name))
56
+ model_class.attributes.should include(AttributeDefinition.new(:first_name))
47
57
  end
48
58
 
49
59
  it "returns the attribute definition" do
@@ -51,8 +61,8 @@ module ActiveAttr
51
61
  end
52
62
 
53
63
  it "defines an attribute reader that calls #attribute" do
54
- subject.should_receive(:attribute).with("name")
55
- subject.name
64
+ subject.should_receive(:attribute).with("first_name")
65
+ subject.first_name
56
66
  end
57
67
 
58
68
  it "defines an attribute reader that can be called via super" do
@@ -61,8 +71,8 @@ module ActiveAttr
61
71
  end
62
72
 
63
73
  it "defines an attribute writer that calls #attribute=" do
64
- subject.should_receive(:attribute=).with("name", "Ben")
65
- subject.name = "Ben"
74
+ subject.should_receive(:attribute=).with("first_name", "Ben")
75
+ subject.first_name = "Ben"
66
76
  end
67
77
 
68
78
  it "defines an attribute writer that can be called via super" do
@@ -70,7 +80,7 @@ module ActiveAttr
70
80
  subject.amount = 1
71
81
  end
72
82
 
73
- it "defining an attribute twice does not make appear in the" do
83
+ it "defining an attribute twice does not give the class two attribute definitions" do
74
84
  Class.new do
75
85
  include Attributes
76
86
  attribute :name
@@ -93,7 +103,7 @@ module ActiveAttr
93
103
  end
94
104
 
95
105
  it "renders the attribute names in alphabetical order" do
96
- model_class.inspect.should match "(amount, name)"
106
+ model_class.inspect.should match "(amount, first_name, last_name)"
97
107
  end
98
108
 
99
109
  it "doesn't format the inspection string for attributes if the model does not have any" do
@@ -109,7 +119,7 @@ module ActiveAttr
109
119
  end
110
120
 
111
121
  it "returns false when compared to another type" do
112
- should_not == Struct.new(:attributes).new("name" => "Ben")
122
+ should_not == Struct.new(:attributes).new("first_name" => "Ben")
113
123
  end
114
124
  end
115
125
 
@@ -122,32 +132,23 @@ module ActiveAttr
122
132
 
123
133
  context "when an attribute is defined" do
124
134
  it "returns the key value pairs" do
125
- subject.name = "Ben"
126
- subject.attributes.should include("name" => "Ben")
135
+ subject.first_name = "Ben"
136
+ subject.attributes.should include("first_name" => "Ben")
127
137
  end
128
138
 
129
139
  it "returns a new Hash " do
130
- subject.attributes.merge!("name" => "Bob")
131
- subject.attributes.should_not include("name" => "Bob")
140
+ subject.attributes.merge!("first_name" => "Bob")
141
+ subject.attributes.should_not include("first_name" => "Bob")
132
142
  end
133
143
 
134
144
  it "returns all attributes" do
135
- subject.attributes.keys.should =~ %w(amount name)
145
+ subject.attributes.keys.should =~ %w(amount first_name last_name)
136
146
  end
137
147
  end
138
148
 
139
149
  context "when a getter is overridden" do
140
- before do
141
- subject.extend Module.new {
142
- def name
143
- "Benjamin"
144
- end
145
- }
146
- end
147
-
148
150
  it "uses the overridden implementation" do
149
- subject.name = "Ben"
150
- subject.attributes.should include("name" => "Benjamin")
151
+ subject.attributes.should include("last_name" => last_name)
151
152
  end
152
153
  end
153
154
  end
@@ -159,14 +160,20 @@ module ActiveAttr
159
160
  end
160
161
 
161
162
  describe "#inspect" do
162
- before { subject.name = "Ben" }
163
+ before { subject.first_name = "Ben" }
163
164
 
164
165
  it "includes the class name and all attribute values in alphabetical order by attribute name" do
165
- subject.inspect.should == %q{#<Foo amount: nil, name: "Ben">}
166
+ subject.inspect.should == %{#<Foo amount: nil, first_name: "Ben", last_name: "#{last_name}">}
166
167
  end
167
168
 
168
169
  it "doesn't format the inspection string for attributes if the model does not have any" do
169
- attributeless.new.inspect.should == %q{#<Foo>}
170
+ attributeless.new.inspect.should == %{#<Foo>}
171
+ end
172
+
173
+ context "when a getter is overridden" do
174
+ it "uses the overridden implementation" do
175
+ subject.inspect.should include %{last_name: "#{last_name}"}
176
+ end
170
177
  end
171
178
  end
172
179
 
@@ -174,42 +181,69 @@ module ActiveAttr
174
181
  describe "##{method}" do
175
182
  context "when an attribute is not set" do
176
183
  it "returns nil" do
177
- subject.send(method, :name).should == nil
184
+ subject.send(method, :first_name).should be_nil
178
185
  end
179
186
  end
180
187
 
181
188
  context "when an attribute is set" do
182
- let(:name) { "Bob" }
189
+ let(:first_name) { "Bob" }
183
190
 
184
- before { subject.write_attribute(:name, name) }
191
+ before { subject.write_attribute(:first_name, first_name) }
185
192
 
186
193
  it "returns the attribute using a Symbol" do
187
- subject.send(method, :name).should == name
194
+ subject.send(method, :first_name).should == first_name
188
195
  end
189
196
 
190
197
  it "returns the attribute using a String" do
191
- subject.send(method, 'name').should == name
198
+ subject.send(method, 'first_name').should == first_name
192
199
  end
193
200
  end
201
+
202
+ context "when the getter is overridden" do
203
+ it "uses the overridden implementation" do
204
+ subject.send(method, :last_name).should == last_name
205
+ end
206
+ end
207
+
208
+ it "raises when getting an undefined attribute" do
209
+ expect do
210
+ subject.send(method, :initials)
211
+ end.to raise_error UnknownAttributeError, "unknown attribute: initials"
212
+ end
194
213
  end
195
214
  end
196
215
 
197
216
  [:[]=, :write_attribute].each do |method|
198
217
  describe "##{method}" do
199
218
  it "raises ArgumentError with one argument" do
200
- expect { subject.send(method, :name) }.to raise_error(ArgumentError)
219
+ expect { subject.send(method, :first_name) }.to raise_error(ArgumentError)
201
220
  end
202
221
 
203
222
  it "raises ArgumentError with no arguments" do
204
223
  expect { subject.send(method) }.to raise_error(ArgumentError)
205
224
  end
206
225
 
207
- it "assigns sets an attribute using a Symbol and value" do
208
- expect { subject.send(method, :name, "Ben") }.to change { subject.attributes["name"] }.from(nil).to("Ben")
226
+ it "sets an attribute using a Symbol and value" do
227
+ expect { subject.send(method, :first_name, "Ben") }.to change { subject.attributes["first_name"] }.from(nil).to("Ben")
228
+ end
229
+
230
+ it "sets an attribute using a String and value" do
231
+ expect { subject.send(method, 'first_name', "Ben") }.to change { subject.attributes["first_name"] }.from(nil).to("Ben")
232
+ end
233
+
234
+ it "is able to set an attribute to nil" do
235
+ subject.first_name = "Ben"
236
+ expect { subject.send(method, :first_name, nil) }.to change { subject.attributes["first_name"] }.from("Ben").to(nil)
237
+ end
238
+
239
+ it "uses the overridden implementation when the setter is overridden" do
240
+ subject.send(method, :last_name, "poweski").should == "POWESKI"
209
241
  end
210
242
 
211
- it "assigns sets an attribute using a String and value" do
212
- expect { subject.send(method, 'name', "Ben") }.to change { subject.attributes["name"] }.from(nil).to("Ben")
243
+ it "raises when setting an undefined attribute" do
244
+ expect do
245
+ subject.send(method, :initials, "BP")
246
+ end.to raise_error UnknownAttributeError, "unknown attribute: initials"
213
247
  end
214
248
  end
215
249
  end