active_attr 0.2.2 → 0.3.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.

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