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

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