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.

@@ -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,5 @@
1
+ require "active_attr/matchers"
2
+
3
+ RSpec.configure do |config|
4
+ config.include ActiveAttr::Matchers
5
+ 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
@@ -1,3 +1,5 @@
1
1
  module ActiveAttr
2
- VERSION = "0.1.0"
2
+ # Complete version string
3
+ # @since 0.1.0
4
+ VERSION = "0.2.0"
3
5
  end
@@ -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
@@ -0,0 +1,8 @@
1
+ require "spec_helper"
2
+ require "active_attr/error"
3
+
4
+ module ActiveAttr
5
+ describe Error do
6
+ it { should be_a_kind_of Module }
7
+ end
8
+ 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
- Class.new do
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
- let(:person) { subject.new }
20
- let(:first_name) { "Chris" }
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 "#initialize" do
58
- it "raises ArgumentError when called with three arguments" do
59
- expect { subject.new({}, {}, nil) }.to raise_error ArgumentError
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