active_attr 0.5.0.alpha2 → 0.5.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.

Files changed (30) hide show
  1. data/.rvmrc +29 -3
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +14 -1
  4. data/Gemfile +2 -1
  5. data/README.md +21 -14
  6. data/lib/active_attr/chainable_initialization.rb +2 -2
  7. data/lib/active_attr/matchers/have_attribute_matcher.rb +84 -22
  8. data/lib/active_attr/model.rb +2 -0
  9. data/lib/active_attr/query_attributes.rb +1 -2
  10. data/lib/active_attr/typecasted_attributes.rb +22 -6
  11. data/lib/active_attr/typecasting.rb +19 -7
  12. data/lib/active_attr/typecasting/big_decimal_typecaster.rb +36 -0
  13. data/lib/active_attr/typecasting/boolean.rb +2 -0
  14. data/lib/active_attr/typecasting/boolean_typecaster.rb +26 -4
  15. data/lib/active_attr/typecasting/date_time_typecaster.rb +19 -2
  16. data/lib/active_attr/typecasting/date_typecaster.rb +18 -2
  17. data/lib/active_attr/typecasting/float_typecaster.rb +18 -2
  18. data/lib/active_attr/typecasting/integer_typecaster.rb +19 -2
  19. data/lib/active_attr/typecasting/object_typecaster.rb +16 -0
  20. data/lib/active_attr/typecasting/string_typecaster.rb +18 -2
  21. data/lib/active_attr/version.rb +1 -1
  22. data/spec/functional/active_attr/matchers/have_attribute_matcher_spec.rb +364 -0
  23. data/spec/functional/active_attr/model_spec.rb +6 -0
  24. data/spec/functional/active_attr/typecasted_attributes_spec.rb +18 -8
  25. data/spec/unit/active_attr/matchers/have_attribute_matcher_spec.rb +19 -144
  26. data/spec/unit/active_attr/matchers_spec.rb +23 -0
  27. data/spec/unit/active_attr/typecasting/big_decimal_typecaster_spec.rb +39 -0
  28. data/spec/unit/active_attr/typecasting/date_time_typecaster_spec.rb +3 -2
  29. data/spec/unit/active_attr/typecasting_spec.rb +5 -0
  30. metadata +30 -20
@@ -0,0 +1,36 @@
1
+ require "bigdecimal"
2
+ require "bigdecimal/util"
3
+ require "active_support/core_ext/big_decimal/conversions"
4
+
5
+ module ActiveAttr
6
+ module Typecasting
7
+ # Typecasts an Object to a BigDecimal
8
+ #
9
+ # @example Usage
10
+ # BigDecimalTypecaster.new.call(1).to_s #=> "0.1E1"
11
+ #
12
+ # @since 0.5.0
13
+ class BigDecimalTypecaster
14
+ # Typecasts an object to a BigDecimal
15
+ #
16
+ # Attempt to convert using #to_d, else it creates a BigDecimal using the
17
+ # String representation of the value.
18
+ #
19
+ # @example Typecast a Fixnum
20
+ # typecaster.call(1).to_s #=> "0.1E1"
21
+ #
22
+ # @param [Object, #to_d, #to_s] value The object to typecast
23
+ #
24
+ # @return [BigDecimal, nil] The result of typecasting
25
+ #
26
+ # @since 0.5.0
27
+ def call(value)
28
+ if value.respond_to? :to_d
29
+ value.to_d
30
+ else
31
+ BigDecimal.new value.to_s
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,7 @@
1
1
  module ActiveAttr
2
2
  module Typecasting
3
+ # Boolean provides a constant to use in typecaster resolution
4
+ # @since 0.5.0
3
5
  class Boolean
4
6
  end
5
7
  end
@@ -2,13 +2,35 @@ require "active_support/core_ext/object/blank"
2
2
 
3
3
  module ActiveAttr
4
4
  module Typecasting
5
- # TODO documentation
5
+ # Typecasts an Object to true or false
6
+ #
7
+ # @example Usage
8
+ # BooleanTypecaster.new.call(1) #=> true
9
+ #
10
+ # @since 0.5.0
6
11
  class BooleanTypecaster
7
- # TODO documentation
8
- # http://yaml.org/type/bool.html
12
+ # Values which force a false result for typecasting
13
+ #
14
+ # These values are based on the
15
+ # {YAML language}[http://yaml.org/type/bool.html].
16
+ #
17
+ # @since 0.5.0
9
18
  FALSE_VALUES = ["n", "N", "no", "No", "NO", "false", "False", "FALSE", "off", "Off", "OFF", "f", "F"]
10
19
 
11
- # TODO documentation
20
+ # Typecasts an object to true or false
21
+ #
22
+ # Similar to ActiveRecord, when the attribute is a zero value or
23
+ # is a string that represents false, typecasting returns false.
24
+ # Otherwise typecasting just checks the presence of a value.
25
+ #
26
+ # @example Typecast a Fixnum
27
+ # typecaster.call(1) #=> true
28
+ #
29
+ # @param [Object] value The object to typecast
30
+ #
31
+ # @return [true, false] The result of typecasting
32
+ #
33
+ # @since 0.5.0
12
34
  def call(value)
13
35
  case value
14
36
  when *FALSE_VALUES then false
@@ -3,9 +3,26 @@ require "active_support/time"
3
3
 
4
4
  module ActiveAttr
5
5
  module Typecasting
6
- # TODO documentation
6
+ # Typecasts an Object to a DateTime
7
+ #
8
+ # @example Usage
9
+ # typecaster = DateTimeTypecaster.new
10
+ # typecaster.call("2012-01-01") #=> Sun, 01 Jan 2012 00:00:00 +0000
11
+ #
12
+ # @since 0.5.0
7
13
  class DateTimeTypecaster
8
- # TODO documentation
14
+ # Typecasts an object to a DateTime
15
+ #
16
+ # Attempts to convert using #to_datetime.
17
+ #
18
+ # @example Typecast a String
19
+ # typecaster.call("2012-01-01") #=> Sun, 01 Jan 2012 00:00:00 +0000
20
+ #
21
+ # @param [Object, #to_datetime] value The object to typecast
22
+ #
23
+ # @return [DateTime, nil] The result of typecasting
24
+ #
25
+ # @since 0.5.0
9
26
  def call(value)
10
27
  value.to_datetime if value.respond_to? :to_datetime
11
28
  end
@@ -3,9 +3,25 @@ require "active_support/time"
3
3
 
4
4
  module ActiveAttr
5
5
  module Typecasting
6
- # TODO documentation
6
+ # Typecasts an Object to a Date
7
+ #
8
+ # @example Usage
9
+ # DateTypecaster.new.call("2012-01-01") #=> Sun, 01 Jan 2012
10
+ #
11
+ # @since 0.5.0
7
12
  class DateTypecaster
8
- # TODO documentation
13
+ # Typecasts an object to a Date
14
+ #
15
+ # Attempts to convert using #to_date.
16
+ #
17
+ # @example Typecast a String
18
+ # typecaster.call("2012-01-01") #=> Sun, 01 Jan 2012
19
+ #
20
+ # @param [Object, #to_date] value The object to typecast
21
+ #
22
+ # @return [Date, nil] The result of typecasting
23
+ #
24
+ # @since 0.5.0
9
25
  def call(value)
10
26
  value.to_date if value.respond_to? :to_date
11
27
  end
@@ -1,8 +1,24 @@
1
1
  module ActiveAttr
2
2
  module Typecasting
3
- # TODO documentation
3
+ # Typecasts an Object to a Float
4
+ #
5
+ # @example Usage
6
+ # FloatTypecaster.new.call(1) #=> 1.0
7
+ #
8
+ # @since 0.5.0
4
9
  class FloatTypecaster
5
- # TODO documentation
10
+ # Typecasts an object to a Float
11
+ #
12
+ # Attempts to convert using #to_f.
13
+ #
14
+ # @example Typecast a Fixnum
15
+ # typecaster.call(1) #=> 1.0
16
+ #
17
+ # @param [Object, #to_f] value The object to typecast
18
+ #
19
+ # @return [Float, nil] The result of typecasting
20
+ #
21
+ # @since 0.5.0
6
22
  def call(value)
7
23
  value.to_f if value.respond_to? :to_f
8
24
  end
@@ -1,8 +1,25 @@
1
1
  module ActiveAttr
2
2
  module Typecasting
3
- # TODO documentation
3
+ # Typecasts an Object to an Integer
4
+ #
5
+ # @example Usage
6
+ # IntegerTypecaster.new.call("1") #=> 1
7
+ #
8
+ # @since 0.5.0
4
9
  class IntegerTypecaster
5
- # TODO documentation
10
+ # Typecasts an object to an Integer
11
+ #
12
+ # Attempts to convert using #to_i. Handles FloatDomainError if the
13
+ # object is INFINITY or NAN.
14
+ #
15
+ # @example Typecast a String
16
+ # typecaster.call("1") #=> 1
17
+ #
18
+ # @param [Object, #to_i] value The object to typecast
19
+ #
20
+ # @return [Integer, nil] The result of typecasting
21
+ #
22
+ # @since 0.5.0
6
23
  def call(value)
7
24
  value.to_i if value.respond_to? :to_i
8
25
  rescue FloatDomainError
@@ -1,6 +1,22 @@
1
1
  module ActiveAttr
2
2
  module Typecasting
3
+ # A "null" typecaster to provide uniformity
4
+ #
5
+ # @example Usage
6
+ # ObjectTypecaster.new.call("") #=> ""
7
+ #
8
+ # @since 0.5.0
3
9
  class ObjectTypecaster
10
+ # Returns the original value unmodified
11
+ #
12
+ # @example Typecast an Object
13
+ # typecaster.call(1) #=> 1
14
+ #
15
+ # @param [Object] value The object to typecast
16
+ #
17
+ # @return [Object] Whatever you passed in
18
+ #
19
+ # @since 0.5.0
4
20
  def call(value)
5
21
  value
6
22
  end
@@ -1,8 +1,24 @@
1
1
  module ActiveAttr
2
2
  module Typecasting
3
- # TODO documentation
3
+ # Typecasts an Object to a String
4
+ #
5
+ # @example Usage
6
+ # StringTypecaster.new.call(1) #=> "1"
7
+ #
8
+ # @since 0.5.0
4
9
  class StringTypecaster
5
- # TODO documentation
10
+ # Typecasts an object to a String
11
+ #
12
+ # Attempts to convert using #to_s.
13
+ #
14
+ # @example Typecast a Fixnum
15
+ # typecaster.call(1) #=> "1"
16
+ #
17
+ # @param [Object, #to_s] value The object to typecast
18
+ #
19
+ # @return [String, nil] The result of typecasting
20
+ #
21
+ # @since 0.5.0
6
22
  def call(value)
7
23
  value.to_s if value.respond_to? :to_s
8
24
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveAttr
2
2
  # Complete version string
3
3
  # @since 0.1.0
4
- VERSION = "0.5.0.alpha2"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -0,0 +1,364 @@
1
+ require "spec_helper"
2
+ require "active_attr/attributes"
3
+ require "active_attr/attribute_defaults"
4
+ require "active_attr/matchers/have_attribute_matcher"
5
+ require "active_attr/typecasted_attributes"
6
+
7
+ module ActiveAttr
8
+ module Matchers
9
+ describe HaveAttributeMatcher do
10
+ let :model_class do
11
+ Class.new do
12
+ include Attributes
13
+ include AttributeDefaults
14
+ include TypecastedAttributes
15
+
16
+ def self.name
17
+ "Person"
18
+ end
19
+ end
20
+ end
21
+
22
+ shared_examples "a matcher matching a class without ActiveAttr::Attributes" do
23
+ let :model_class do
24
+ Class.new do
25
+ def self.name
26
+ "Person"
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "#matches?" do
32
+ it { subject.matches?(model_class).should be_false }
33
+ end
34
+
35
+ describe "#failure_message" do
36
+ before { subject.matches?(model_class) }
37
+
38
+ it { subject.failure_message.should == %{expected #{model_class.name} to include ActiveAttr::Attributes} }
39
+ end
40
+ end
41
+
42
+ shared_examples "a matcher matching a class without ActiveAttr::AttributeDefaults" do
43
+ let :model_class do
44
+ Class.new do
45
+ include Attributes
46
+
47
+ def self.name
48
+ "Person"
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "#matches?" do
54
+ it { subject.matches?(model_class).should be_false }
55
+ end
56
+
57
+ describe "#failure_message" do
58
+ before { subject.matches?(model_class) }
59
+
60
+ it { subject.failure_message.should == %{expected #{model_class.name} to include ActiveAttr::AttributeDefaults} }
61
+ end
62
+ end
63
+
64
+ shared_examples "a matcher matching a class without ActiveAttr::TypecastedAttributes" do
65
+ let :model_class do
66
+ Class.new do
67
+ include Attributes
68
+
69
+ def self.name
70
+ "Person"
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "#matches?" do
76
+ it { subject.matches?(model_class).should be_false }
77
+ end
78
+
79
+ describe "#failure_message" do
80
+ before { subject.matches?(model_class) }
81
+
82
+ it { subject.failure_message.should == %{expected #{model_class.name} to include ActiveAttr::TypecastedAttributes} }
83
+ end
84
+ end
85
+
86
+ context "a matcher with just an attribute name" do
87
+ subject { described_class.new(:first_name) }
88
+
89
+ it_should_behave_like "a matcher matching a class without ActiveAttr::Attributes"
90
+
91
+ context "a class with the attribute" do
92
+ before { model_class.attribute :first_name }
93
+
94
+ describe "#matches?" do
95
+ it { subject.matches?(model_class).should be_true }
96
+ end
97
+
98
+ describe "#negative_failure_message" do
99
+ before { subject.matches?(model_class) }
100
+
101
+ it { subject.negative_failure_message.should == %{expected Person to not have attribute named first_name} }
102
+ end
103
+ end
104
+
105
+ context "a class without the attribute" do
106
+ describe "#matches?" do
107
+ it { subject.matches?(model_class).should be_false }
108
+ end
109
+
110
+ describe "#failure_message" do
111
+ before { subject.matches?(model_class) }
112
+
113
+ it { subject.failure_message.should == %{expected Person to have attribute named first_name} }
114
+ end
115
+ end
116
+ end
117
+
118
+ context "a matcher with a default value" do
119
+ subject { described_class.new(:first_name).with_default_value_of("John") }
120
+
121
+ it_should_behave_like "a matcher matching a class without ActiveAttr::Attributes"
122
+ it_should_behave_like "a matcher matching a class without ActiveAttr::AttributeDefaults"
123
+
124
+ context "a class with the attribute but no default" do
125
+ before { model_class.attribute :first_name }
126
+
127
+ describe "#matches?" do
128
+ it { subject.matches?(model_class).should be_false }
129
+ end
130
+
131
+ describe "#failure_message" do
132
+ before { subject.matches?(model_class) }
133
+
134
+ it { subject.failure_message.should == %{expected Person to have attribute named first_name with a default value of "John"} }
135
+ end
136
+ end
137
+
138
+ context "a class with the attribute and a different default" do
139
+ before { model_class.attribute :first_name, :default => "Doe" }
140
+
141
+ describe "#matches?" do
142
+ it { subject.matches?(model_class).should be_false }
143
+ end
144
+
145
+ describe "#failure_message" do
146
+ before { subject.matches?(model_class) }
147
+
148
+ it { subject.failure_message.should == %{expected Person to have attribute named first_name with a default value of "John"} }
149
+ end
150
+ end
151
+
152
+ context "a class with the attribute and the right default" do
153
+ before { model_class.attribute :first_name, :default => "John" }
154
+
155
+ describe "#matches?" do
156
+ it { subject.matches?(model_class).should be_true }
157
+ end
158
+
159
+ describe "#negative_failure_message" do
160
+ before { subject.matches?(model_class) }
161
+
162
+ it { subject.negative_failure_message.should == %{expected Person to not have attribute named first_name with a default value of "John"} }
163
+ end
164
+ end
165
+ end
166
+
167
+ context "a matcher with a default value of false" do
168
+ subject { described_class.new(:admin).with_default_value_of(false) }
169
+
170
+ it_should_behave_like "a matcher matching a class without ActiveAttr::Attributes"
171
+ it_should_behave_like "a matcher matching a class without ActiveAttr::AttributeDefaults"
172
+
173
+ context "a class with the attribute but no default" do
174
+ before { model_class.attribute :admin }
175
+
176
+ describe "#matches?" do
177
+ it { subject.matches?(model_class).should be_false }
178
+ end
179
+
180
+ describe "#failure_message" do
181
+ before { subject.matches?(model_class) }
182
+
183
+ it { subject.failure_message.should == %{expected Person to have attribute named admin with a default value of false} }
184
+ end
185
+ end
186
+
187
+ context "a class with the attribute and a default of nil" do
188
+ before { model_class.attribute :admin, :default => nil }
189
+
190
+ describe "#matches?" do
191
+ it { subject.matches?(model_class).should be_false }
192
+ end
193
+
194
+ describe "#failure_message" do
195
+ before { subject.matches?(model_class) }
196
+
197
+ it { subject.failure_message.should == %{expected Person to have attribute named admin with a default value of false} }
198
+ end
199
+ end
200
+
201
+ context "a class with the attribute and the right default" do
202
+ before { model_class.attribute :admin, :default => false }
203
+
204
+ describe "#matches?" do
205
+ it { subject.matches?(model_class).should be_true }
206
+ end
207
+
208
+ describe "#negative_failure_message" do
209
+ before { subject.matches?(model_class) }
210
+
211
+ it { subject.negative_failure_message.should == %{expected Person to not have attribute named admin with a default value of false} }
212
+ end
213
+ end
214
+ end
215
+
216
+ context "a matcher with a default value of nil" do
217
+ subject { described_class.new(:first_name).with_default_value_of(nil) }
218
+
219
+ it_should_behave_like "a matcher matching a class without ActiveAttr::Attributes"
220
+ it_should_behave_like "a matcher matching a class without ActiveAttr::AttributeDefaults"
221
+
222
+ context "a class with the attribute but no default" do
223
+ before { model_class.attribute :first_name }
224
+
225
+ describe "#matches?" do
226
+ it { subject.matches?(model_class).should be_true }
227
+ end
228
+
229
+ describe "#negative_failure_message" do
230
+ before { subject.matches?(model_class) }
231
+
232
+ it { subject.negative_failure_message.should == %{expected Person to not have attribute named first_name with a default value of nil} }
233
+ end
234
+ end
235
+
236
+ context "a class with the attribute and a default of false" do
237
+ before { model_class.attribute :first_name, :default => false }
238
+
239
+ describe "#matches?" do
240
+ it { subject.matches?(model_class).should be_false }
241
+ end
242
+
243
+ describe "#failure_message" do
244
+ before { subject.matches?(model_class) }
245
+
246
+ it { subject.failure_message.should == %{expected Person to have attribute named first_name with a default value of nil} }
247
+ end
248
+ end
249
+
250
+ context "a class with the attribute and the right default" do
251
+ before { model_class.attribute :first_name, :default => nil }
252
+
253
+ describe "#matches?" do
254
+ it { subject.matches?(model_class).should be_true }
255
+ end
256
+
257
+ describe "#negative_failure_message" do
258
+ before { subject.matches?(model_class) }
259
+
260
+ it { subject.negative_failure_message.should == %{expected Person to not have attribute named first_name with a default value of nil} }
261
+ end
262
+ end
263
+ end
264
+
265
+ context "a matcher with a type" do
266
+ subject { described_class.new(:first_name).of_type(String) }
267
+
268
+ it_should_behave_like "a matcher matching a class without ActiveAttr::Attributes"
269
+ it_should_behave_like "a matcher matching a class without ActiveAttr::TypecastedAttributes"
270
+
271
+ context "a class with the attribute but no type" do
272
+ before { model_class.attribute :first_name }
273
+
274
+ describe "#matches?" do
275
+ it { subject.matches?(model_class).should be_false }
276
+ end
277
+
278
+ describe "#failure_message" do
279
+ before { subject.matches?(model_class) }
280
+
281
+ it { subject.failure_message.should == %{expected Person to have attribute named first_name of type String} }
282
+ end
283
+ end
284
+
285
+ context "a class with the attribute and a different type" do
286
+ before { model_class.attribute :first_name, :type => Symbol }
287
+
288
+ describe "#matches?" do
289
+ it { subject.matches?(model_class).should be_false }
290
+ end
291
+
292
+ describe "#failure_message" do
293
+ before { subject.matches?(model_class) }
294
+
295
+ it { subject.failure_message.should == %{expected Person to have attribute named first_name of type String} }
296
+ end
297
+ end
298
+
299
+ context "a class with the attribute and the right type" do
300
+ before { model_class.attribute :first_name, :type => String }
301
+
302
+ describe "#matches?" do
303
+ it { subject.matches?(model_class).should be_true }
304
+ end
305
+
306
+ describe "#negative_failure_message" do
307
+ before { subject.matches?(model_class) }
308
+
309
+ it { subject.negative_failure_message.should == %{expected Person to not have attribute named first_name of type String} }
310
+ end
311
+ end
312
+ end
313
+
314
+ context "a matcher with a type of Object" do
315
+ subject { described_class.new(:first_name).of_type(Object) }
316
+
317
+ it_should_behave_like "a matcher matching a class without ActiveAttr::Attributes"
318
+ it_should_behave_like "a matcher matching a class without ActiveAttr::TypecastedAttributes"
319
+
320
+ context "a class with the attribute but no type" do
321
+ before { model_class.attribute :first_name }
322
+
323
+ describe "#matches?" do
324
+ it { subject.matches?(model_class).should be_true }
325
+ end
326
+
327
+ describe "#negative_failure_message" do
328
+ before { subject.matches?(model_class) }
329
+
330
+ it { subject.negative_failure_message.should == %{expected Person to not have attribute named first_name of type Object} }
331
+ end
332
+ end
333
+
334
+ context "a class with the attribute and a different type" do
335
+ before { model_class.attribute :first_name, :type => String }
336
+
337
+ describe "#matches?" do
338
+ it { subject.matches?(model_class).should be_false }
339
+ end
340
+
341
+ describe "#failure_message" do
342
+ before { subject.matches?(model_class) }
343
+
344
+ it { subject.failure_message.should == %{expected Person to have attribute named first_name of type Object} }
345
+ end
346
+ end
347
+
348
+ context "a class with the attribute and the right type" do
349
+ before { model_class.attribute :first_name, :type => Object }
350
+
351
+ describe "#matches?" do
352
+ it { subject.matches?(model_class).should be_true }
353
+ end
354
+
355
+ describe "#negative_failure_message" do
356
+ before { subject.matches?(model_class) }
357
+
358
+ it { subject.negative_failure_message.should == %{expected Person to not have attribute named first_name of type Object} }
359
+ end
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end