active_attr 0.1.0 → 0.2.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.
- data/.document +1 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +14 -2
- data/Gemfile +4 -1
- data/Guardfile +5 -4
- data/MIT-LICENSE +1 -1
- data/README.md +52 -1
- data/active_attr.gemspec +2 -2
- data/lib/active_attr.rb +16 -1
- data/lib/active_attr/attribute_definition.rb +52 -0
- data/lib/active_attr/attributes.rb +138 -0
- data/lib/active_attr/basic_model.rb +25 -0
- data/lib/active_attr/error.rb +16 -0
- data/lib/active_attr/mass_assignment.rb +48 -7
- data/lib/active_attr/matchers.rb +13 -0
- data/lib/active_attr/matchers/have_attribute_matcher.rb +69 -0
- data/lib/active_attr/rspec.rb +5 -0
- data/lib/active_attr/strict_mass_assignment.rb +44 -0
- data/lib/active_attr/unknown_attributes_error.rb +18 -0
- data/lib/active_attr/version.rb +3 -1
- data/spec/active_attr/attribute_definition_spec.rb +46 -0
- data/spec/active_attr/attributes_spec.rb +141 -0
- data/spec/active_attr/basic_model_spec.rb +19 -0
- data/spec/active_attr/error_spec.rb +8 -0
- data/spec/active_attr/mass_assignment_spec.rb +7 -95
- data/spec/active_attr/matchers/have_attribute_matcher_spec.rb +103 -0
- data/spec/active_attr/strict_mass_assignment_spec.rb +38 -0
- data/spec/active_attr/unknown_attributes_error_spec.rb +9 -0
- data/spec/active_attr/version_spec.rb +35 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/active_model_lint.rb +1 -1
- data/spec/support/mass_assignment_shared_examples.rb +95 -0
- metadata +44 -14
@@ -0,0 +1,13 @@
|
|
1
|
+
require "active_attr/matchers/have_attribute_matcher"
|
2
|
+
|
3
|
+
module ActiveAttr
|
4
|
+
# Matchers that can be used with RSpec and Shoulda to declaritively verify
|
5
|
+
# models based on ActiveAttr modules
|
6
|
+
#
|
7
|
+
# @example Integrate with RSPec
|
8
|
+
# require "active_attr/rspec"
|
9
|
+
#
|
10
|
+
# @since 0.2.0
|
11
|
+
module Matchers
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActiveAttr
|
2
|
+
module Matchers
|
3
|
+
# Specify that a model should have an attribute matching the criteria
|
4
|
+
#
|
5
|
+
# @example Person should have a name attribute
|
6
|
+
# describe Person do
|
7
|
+
# it { should have_attribute(:name) }
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# @param [Symbol, String, #to_sym] attribute_name
|
11
|
+
#
|
12
|
+
# @return [HaveAttributeMatcher]
|
13
|
+
#
|
14
|
+
# @since 0.2.0
|
15
|
+
def have_attribute(attribute_name)
|
16
|
+
HaveAttributeMatcher.new(attribute_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Verifies that an ActiveAttr-based model has an attribute matching the
|
20
|
+
# given criteria
|
21
|
+
#
|
22
|
+
# @since 0.2.0
|
23
|
+
class HaveAttributeMatcher
|
24
|
+
# @return [Symbol]
|
25
|
+
# @api private
|
26
|
+
attr_reader :attribute_name
|
27
|
+
|
28
|
+
# @return [String] Description
|
29
|
+
# @api private
|
30
|
+
def description
|
31
|
+
"have attribute named #{attribute_name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String] Failure message
|
35
|
+
# @api private
|
36
|
+
def failure_message
|
37
|
+
"Expected #{@model_class} to #{description}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [Symbol, String, #to_sym] attribute_name
|
41
|
+
# @api private
|
42
|
+
def initialize(attribute_name)
|
43
|
+
raise TypeError, "can't convert #{attribute_name.class} into Symbol" unless attribute_name.respond_to? :to_sym
|
44
|
+
@attribute_name = attribute_name.to_sym
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
def matches?(model_or_model_class)
|
49
|
+
@model_class = class_from(model_or_model_class)
|
50
|
+
|
51
|
+
@model_class.attributes.any? do |attribute|
|
52
|
+
attribute.name == attribute_name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [String] Negative failure message
|
57
|
+
# @api private
|
58
|
+
def negative_failure_message
|
59
|
+
"Expected #{@model_class} to not #{description}"
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def class_from(object)
|
65
|
+
Class === object ? object : object.class
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "active_attr/mass_assignment"
|
2
|
+
require "active_attr/unknown_attributes_error"
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module ActiveAttr
|
6
|
+
# StrictMassAssignment allows mass assignment of attributes, but raises an
|
7
|
+
# exception when assigning unknown attributes
|
8
|
+
#
|
9
|
+
# Attempting to assign any unknown or private attribute through any of the
|
10
|
+
# mass assignment methods ({#assign_attributes}, {#attributes=}, and
|
11
|
+
# {#initialize}) will raise an {ActiveAttr::UnknownAttributesError}
|
12
|
+
# exception.
|
13
|
+
#
|
14
|
+
# @example Usage
|
15
|
+
# class Person
|
16
|
+
# include ActiveAttr::StrictMassAssignment
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @since 0.2.0
|
20
|
+
module StrictMassAssignment
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
include MassAssignment
|
23
|
+
|
24
|
+
# Mass update a model's attributes, but raise an error if an attempt is
|
25
|
+
# made to assign an unknown attribute
|
26
|
+
#
|
27
|
+
# @param (see MassAssignment#assign_attributes)
|
28
|
+
#
|
29
|
+
# @raise [ActiveAttr::UnknownAttributesError]
|
30
|
+
#
|
31
|
+
# @since 0.2.0
|
32
|
+
def assign_attributes(new_attributes, options={})
|
33
|
+
unknown_attribute_names = (new_attributes || {}).reject do |name, value|
|
34
|
+
respond_to? "#{name}="
|
35
|
+
end.map { |name, value| name.to_s }.sort
|
36
|
+
|
37
|
+
if unknown_attribute_names.any?
|
38
|
+
raise UnknownAttributesError, "unknown attribute(s): #{unknown_attribute_names.join(", ")}"
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "active_attr/error"
|
2
|
+
|
3
|
+
module ActiveAttr
|
4
|
+
# This exception is raised if attempting to mass assign unknown attributes
|
5
|
+
# when using {StrictMassAssignment}
|
6
|
+
#
|
7
|
+
# @example Rescuing an UnknownAttributesError error
|
8
|
+
# begin
|
9
|
+
# Person.new(attributes)
|
10
|
+
# rescue ActiveAttr::UnknownAttributesError
|
11
|
+
# Person.new
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# @since 0.2.0
|
15
|
+
class UnknownAttributesError < NoMethodError
|
16
|
+
include Error
|
17
|
+
end
|
18
|
+
end
|
data/lib/active_attr/version.rb
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "active_attr/attribute_definition"
|
3
|
+
|
4
|
+
module ActiveAttr
|
5
|
+
describe AttributeDefinition do
|
6
|
+
subject { described_class.new(:amount) }
|
7
|
+
|
8
|
+
describe "#==" do
|
9
|
+
it "returns true when the attribute name is equal" do
|
10
|
+
described_class.new(:amount).should == described_class.new(:amount)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "returns false when another object is compared" do
|
14
|
+
described_class.new(:amount).should_not == Struct.new(:name).new(:amount)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#initialize" do
|
19
|
+
it "raises an ArgumentError when no arguments" do
|
20
|
+
expect { described_class.new }.to raise_error ArgumentError
|
21
|
+
end
|
22
|
+
|
23
|
+
it "assigns the first argument to name" do
|
24
|
+
described_class.new(:amount).name.should == :amount
|
25
|
+
end
|
26
|
+
|
27
|
+
it "converts a String attribute name to a Symbol" do
|
28
|
+
described_class.new('amount').name.should == :amount
|
29
|
+
end
|
30
|
+
|
31
|
+
it "raises a TypeError when the attribute name does not respond to #to_sym" do
|
32
|
+
expect { described_class.new(Object.new) }.to raise_error(TypeError, "can't convert Object into Symbol")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#name" do
|
37
|
+
it { should respond_to(:name) }
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#to_s" do
|
41
|
+
it "renders the name" do
|
42
|
+
subject.to_s.should == subject.name.to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "active_attr/attributes"
|
3
|
+
|
4
|
+
module ActiveAttr
|
5
|
+
describe Attributes do
|
6
|
+
let(:model_class) do
|
7
|
+
Class.new do
|
8
|
+
include Attributes
|
9
|
+
attribute :name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
subject do
|
14
|
+
Class.new do
|
15
|
+
include Attributes
|
16
|
+
attribute :name
|
17
|
+
attribute :amount
|
18
|
+
|
19
|
+
def self.name
|
20
|
+
"Foo"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".attribute" do
|
26
|
+
let(:instance) { subject.new }
|
27
|
+
|
28
|
+
it "creates an attribute with no options" do
|
29
|
+
subject.attributes.should include(AttributeDefinition.new(:name))
|
30
|
+
end
|
31
|
+
|
32
|
+
it "defined an attribute reader that calls #read_attribute" do
|
33
|
+
instance.should_receive(:read_attribute).with(:name)
|
34
|
+
instance.name
|
35
|
+
end
|
36
|
+
|
37
|
+
it "defines an attribute writer method that calls #write_attribute" do
|
38
|
+
instance.should_receive(:write_attribute).with(:name, "Ben")
|
39
|
+
instance.name = "Ben"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe ".attributes" do
|
44
|
+
it { should respond_to(:attributes) }
|
45
|
+
|
46
|
+
context "when no attributes exist" do
|
47
|
+
subject do
|
48
|
+
Class.new { include Attributes }.attributes
|
49
|
+
end
|
50
|
+
|
51
|
+
it "returns an empty Array" do
|
52
|
+
should == []
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe ".inspect" do
|
58
|
+
it "renders the class name" do
|
59
|
+
subject.inspect.should match "Foo"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "renders the attribute name and type" do
|
63
|
+
subject.inspect.should match subject.attributes.map { |a| a.name }.join(", ")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#==" do
|
68
|
+
let(:model_class) do
|
69
|
+
Class.new do
|
70
|
+
include Attributes
|
71
|
+
attribute :name
|
72
|
+
|
73
|
+
def initialize(name)
|
74
|
+
write_attribute(:name, name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
subject { model_class.new("Ben") }
|
80
|
+
|
81
|
+
it "returns true when all attributes are equal" do
|
82
|
+
should == model_class.new("Ben")
|
83
|
+
end
|
84
|
+
|
85
|
+
it "returns false when compared to another type" do
|
86
|
+
should_not == Struct.new(:attributes).new("name" => "Ben")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "#attributes" do
|
91
|
+
context "when no attributes are defined" do
|
92
|
+
subject { Class.new { include Attributes } }
|
93
|
+
|
94
|
+
it "returns an empty Hash" do
|
95
|
+
subject.new.attributes.should == {}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#inspect" do
|
101
|
+
let(:instance) { subject.new.tap { |obj| obj.name = "Ben" } }
|
102
|
+
|
103
|
+
it "includes the class name and all attribute values" do
|
104
|
+
instance.inspect.should == %q{#<Foo name: "Ben", amount: nil>}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#read_attribute" do
|
109
|
+
let(:name) { "Bob" }
|
110
|
+
subject { model_class.new.tap { |s| s.write_attribute(:name, name) } }
|
111
|
+
|
112
|
+
it "returns the attribute using a Symbol" do
|
113
|
+
subject.read_attribute(:name).should == name
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns the attribute using a String" do
|
117
|
+
subject.read_attribute('name').should == name
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#write_attribute" do
|
122
|
+
subject { model_class.new }
|
123
|
+
|
124
|
+
it "raises ArgumentError with one argument" do
|
125
|
+
expect { subject.write_attribute(:name) }.to raise_error(ArgumentError)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "raises ArgumentError with no arguments" do
|
129
|
+
expect { subject.write_attribute }.to raise_error(ArgumentError)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "assigns sets an attribute using a Symbol and value" do
|
133
|
+
expect { subject.write_attribute(:name, "Ben") }.to change(subject, :attributes).from({}).to("name" => "Ben")
|
134
|
+
end
|
135
|
+
|
136
|
+
it "assigns sets an attribute using a String and value" do
|
137
|
+
expect { subject.write_attribute('name', "Ben") }.to change(subject, :attributes).from({}).to("name" => "Ben")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "active_attr/basic_model"
|
3
|
+
|
4
|
+
module ActiveAttr
|
5
|
+
describe BasicModel do
|
6
|
+
let(:model_class) do
|
7
|
+
Class.new do
|
8
|
+
include BasicModel
|
9
|
+
|
10
|
+
def self.name
|
11
|
+
"Foo"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
subject { model_class.new }
|
17
|
+
it_should_behave_like "ActiveModel"
|
18
|
+
end
|
19
|
+
end
|
@@ -2,45 +2,15 @@ require "spec_helper"
|
|
2
2
|
require "active_attr/mass_assignment"
|
3
3
|
|
4
4
|
module ActiveAttr
|
5
|
-
describe MassAssignment do
|
5
|
+
describe MassAssignment, :mass_assignment do
|
6
6
|
subject do
|
7
|
-
|
7
|
+
model_class.class_eval do
|
8
8
|
include MassAssignment
|
9
|
-
|
10
|
-
attr_accessor :first_name, :last_name, :middle_name
|
11
|
-
private :middle_name=
|
12
|
-
|
13
|
-
def name=(name)
|
14
|
-
self.last_name, self.first_name = name.split(nil, 2).reverse
|
15
|
-
end
|
16
9
|
end
|
17
10
|
end
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
let(:last_name) { "Griego" }
|
22
|
-
|
23
|
-
def should_assign_names_to(person)
|
24
|
-
person.first_name.should == first_name
|
25
|
-
person.last_name.should == last_name
|
26
|
-
end
|
27
|
-
|
28
|
-
shared_examples_for "a mass assigning method" do
|
29
|
-
it "does not raise when assigning nil attributes" do
|
30
|
-
expect { mass_assign_attributes nil }.not_to raise_error
|
31
|
-
end
|
32
|
-
|
33
|
-
it "assigns all valid attributes when passed as a hash with string keys" do
|
34
|
-
should_assign_names_to mass_assign_attributes('first_name' => first_name, 'last_name' => last_name)
|
35
|
-
end
|
36
|
-
|
37
|
-
it "assigns all valid attributes when passed as a hash with symbol keys" do
|
38
|
-
should_assign_names_to mass_assign_attributes(:first_name => first_name, :last_name => last_name)
|
39
|
-
end
|
40
|
-
|
41
|
-
it "uses any available writer methods" do
|
42
|
-
should_assign_names_to mass_assign_attributes(:name => "#{first_name} #{last_name}")
|
43
|
-
end
|
12
|
+
shared_examples "lenient mass assignment method", :lenient_mass_assignment_method => true do
|
13
|
+
include_examples "mass assignment method"
|
44
14
|
|
45
15
|
it "ignores attributes which do not have a writer" do
|
46
16
|
person = mass_assign_attributes(:middle_initial => "J")
|
@@ -54,66 +24,8 @@ module ActiveAttr
|
|
54
24
|
end
|
55
25
|
end
|
56
26
|
|
57
|
-
describe "#
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
it "does not raise when called with two arguments" do
|
63
|
-
expect { subject.new({}, {}) }.not_to raise_error
|
64
|
-
end
|
65
|
-
|
66
|
-
it "does not raise when called with a single argument" do
|
67
|
-
expect { subject.new({}) }.not_to raise_error
|
68
|
-
end
|
69
|
-
|
70
|
-
it "does not raise when called with no arguments" do
|
71
|
-
expect { subject.new }.not_to raise_error
|
72
|
-
end
|
73
|
-
|
74
|
-
def mass_assign_attributes(attributes)
|
75
|
-
subject.new(attributes)
|
76
|
-
end
|
77
|
-
|
78
|
-
it_should_behave_like "a mass assigning method"
|
79
|
-
end
|
80
|
-
|
81
|
-
describe "#attributes=" do
|
82
|
-
it "raises ArgumentError when called with two arguments" do
|
83
|
-
expect { person.send(:attributes=, {}, {}) }.to raise_error ArgumentError
|
84
|
-
end
|
85
|
-
|
86
|
-
def mass_assign_attributes(attributes)
|
87
|
-
person.attributes = attributes
|
88
|
-
person
|
89
|
-
end
|
90
|
-
|
91
|
-
it_should_behave_like "a mass assigning method"
|
92
|
-
end
|
93
|
-
|
94
|
-
describe "#assign_attributes" do
|
95
|
-
it "raises ArgumentError when called with three arguments" do
|
96
|
-
expect { subject.new.assign_attributes({}, {}, nil) }.to raise_error ArgumentError
|
97
|
-
end
|
98
|
-
|
99
|
-
it "does not raise when called with two arguments" do
|
100
|
-
expect { subject.new.assign_attributes({}, {}) }.not_to raise_error
|
101
|
-
end
|
102
|
-
|
103
|
-
it "does not raise when called with a single argument" do
|
104
|
-
expect { subject.new.assign_attributes({}) }.not_to raise_error
|
105
|
-
end
|
106
|
-
|
107
|
-
it "raises ArgumentError when called with no arguments" do
|
108
|
-
expect { subject.new.assign_attributes }.to raise_error ArgumentError
|
109
|
-
end
|
110
|
-
|
111
|
-
def mass_assign_attributes(attributes)
|
112
|
-
person.assign_attributes attributes
|
113
|
-
person
|
114
|
-
end
|
115
|
-
|
116
|
-
it_should_behave_like "a mass assigning method"
|
117
|
-
end
|
27
|
+
describe "#assign_attributes", :assign_attributes, :lenient_mass_assignment_method
|
28
|
+
describe "#attributes=", :attributes=, :lenient_mass_assignment_method
|
29
|
+
describe "#initialize", :initialize, :lenient_mass_assignment_method
|
118
30
|
end
|
119
31
|
end
|