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
data/.rvmrc CHANGED
@@ -6,10 +6,25 @@
6
6
  # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
7
  environment_id="ruby-1.9.2-p290@active_attr"
8
8
 
9
+ #
10
+ # Uncomment the following lines if you want to verify rvm version per project
11
+ #
12
+ # rvmrc_rvm_version="1.10.1" # 1.10.1 seams as a safe start
13
+ # eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
14
+ # echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
15
+ # return 1
16
+ # }
17
+ #
18
+
9
19
  #
10
20
  # Uncomment following line if you want options to be set only for given project.
11
21
  #
12
22
  # PROJECT_JRUBY_OPTS=( --1.9 )
23
+ #
24
+ # The variable PROJECT_JRUBY_OPTS requires the following to be run in shell:
25
+ #
26
+ # chmod +x ${rvm_path}/hooks/after_use_jruby_opts
27
+ #
13
28
 
14
29
  #
15
30
  # First we attempt to load the desired environment directly from the environment
@@ -31,7 +46,7 @@ else
31
46
  if ! rvm --create use "$environment_id"
32
47
  then
33
48
  echo "Failed to create RVM environment '${environment_id}'."
34
- exit 1
49
+ return 1
35
50
  fi
36
51
  fi
37
52
 
@@ -47,9 +62,20 @@ fi
47
62
  # fi
48
63
 
49
64
  # If you use bundler, this might be useful to you:
50
- # if command -v bundle && [[ -s Gemfile ]]
65
+ # if [[ -s Gemfile ]] && ! command -v bundle >/dev/null
66
+ # then
67
+ # printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
68
+ # gem install bundler
69
+ # fi
70
+ # if [[ -s Gemfile ]] && command -v bundle
51
71
  # then
52
- # bundle
72
+ # bundle install
53
73
  # fi
54
74
 
75
+ if [[ $- == *i* ]] # check for interactive shells
76
+ then
77
+ echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
78
+ else
79
+ echo "Using: $GEM_HOME" # don't use colors in interactive shells
80
+ fi
55
81
 
data/.travis.yml CHANGED
@@ -1,3 +1,4 @@
1
+ language: ruby
1
2
  rvm:
2
3
  - ree
3
4
  - 1.9.2
data/CHANGELOG.md CHANGED
@@ -1,12 +1,25 @@
1
- # ActiveAttr 0.5.0 (unreleased) #
1
+ # ActiveAttr 0.5.0 (March 11, 2012) #
2
2
 
3
+ * ActiveAttr is now Ruby warning free
3
4
  * Added AttributeDefaults
4
5
  * Added AttributeDefinition#[]
5
6
  * Added Attributes.attribute_names
7
+ * Added Matchers::HaveAttributeMatcher#of_type
6
8
  * Added Matchers::HaveAttributeMatcher#with_default_value_of
7
9
  * Added TypecastedAttributes
8
10
  * Added Typecasting
11
+ * Added Typecasting::BigDecimalTypecaster
12
+ * Added Typecasting::Boolean
13
+ * Added Typecasting::BooleanTypecaster
14
+ * Added Typecasting::DateTimeTypecaster
15
+ * Added Typecasting::DateTypecaster
16
+ * Added Typecasting::FloatTypecaster
17
+ * Added Typecasting::IntegerTypecaster
18
+ * Added Typecasting::ObjectTypecaster
19
+ * Added Typecasting::StringTypecaster
9
20
  * Changed Attributes.attributes return value from an Array to a Hash
21
+ * Changed HaveAttributeMatcher to return spec failures when the model is
22
+ missing ActiveAttr modules
10
23
  * Changed redefining an attribute to actually redefine the attribute
11
24
  * Removed StrictMassAssignment, instead use MassAssignmentSecurity with
12
25
  ActiveModel v3.2 which allows assigning mass_assignment_sanitizer to
data/Gemfile CHANGED
@@ -9,9 +9,10 @@ group :development do
9
9
  gem "guard-rspec"
10
10
  gem "rb-fsevent"
11
11
  gem "rdiscount"
12
- gem "rdoc", "~> 3.9.4"
12
+ gem "rdoc"
13
13
  gem "ruby-debug", :platforms => :mri_18
14
14
  gem "ruby-debug19", :platforms => :mri_19 if RUBY_VERSION < "1.9.3"
15
15
  gem "spec_coverage", :platforms => :mri_19
16
+ gem "travis-lint"
16
17
  gem "yard"
17
18
  end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ActiveAttr #
2
2
 
3
- [![Build History][2]][1]
3
+ [![Build History][2]][1] [![Dependency Status][5]][4]
4
4
 
5
5
  ActiveAttr is a set of modules that makes it easy to create plain old ruby
6
6
  models with functionality found in ORMs, like ActiveRecord, without
@@ -14,19 +14,11 @@ ActiveAttr is distributed as a rubygem [on rubygems.org][3].
14
14
  [1]: http://travis-ci.org/cgriego/active_attr
15
15
  [2]: https://secure.travis-ci.org/cgriego/active_attr.png?branch=master
16
16
  [3]: http://rubygems.org/gems/active_attr
17
+ [4]: https://gemnasium.com/cgriego/active_attr
18
+ [5]: https://gemnasium.com/cgriego/active_attr.png
17
19
 
18
20
  ## Modules ##
19
21
 
20
- ### Model ###
21
-
22
- The Model module is a shortcut for incorporating the most common model
23
- functionality into your model with one include. All of the following modules
24
- are included when you include Model.
25
-
26
- class Person
27
- include ActiveAttr::Model
28
- end
29
-
30
22
  ### Attributes ###
31
23
 
32
24
  Including the Attributes module into your class gives you a DSL for defining
@@ -79,7 +71,8 @@ providing instance methods for querying your attributes.
79
71
 
80
72
  #### TypecastedAttributes ####
81
73
 
82
- TODO documentation
74
+ Including the TypecastedAttributes module into your class builds on Attributes
75
+ by providing type conversion for your attributes.
83
76
 
84
77
  class Person
85
78
  include ActiveAttr::TypecastedAttributes
@@ -166,7 +159,7 @@ MassAssignment methods to honor any declared mass assignment permission
166
159
  blacklists or whitelists including support for mass assignment roles.
167
160
 
168
161
  class Person
169
- include ActiveAttr::MassAssignment
162
+ include ActiveAttr::MassAssignmentSecurity
170
163
  attr_accessor :first_name, :last_name
171
164
  attr_protected :last_name
172
165
  end
@@ -175,6 +168,16 @@ blacklists or whitelists including support for mass assignment roles.
175
168
  person.first_name #=> "Chris"
176
169
  person.last_name #=> nil
177
170
 
171
+ ### Model ###
172
+
173
+ The Model module is a shortcut for incorporating the most common model
174
+ functionality into your model with one include. All of the above modules
175
+ are included when you include Model.
176
+
177
+ class Person
178
+ include ActiveAttr::Model
179
+ end
180
+
178
181
  ## Integrations ##
179
182
 
180
183
  ### Ruby on Rails ###
@@ -193,5 +196,9 @@ your models. The matchers also work with compatible frameworks like Shoulda.
193
196
  require "active_attr/rspec"
194
197
 
195
198
  describe Person do
196
- it { should have_attribute(:first_name).with_default_value_of("John") }
199
+ it do
200
+ should have_attribute(:first_name).
201
+ of_type(String).
202
+ with_default_value_of("John")
203
+ end
197
204
  end
@@ -4,8 +4,8 @@ module ActiveAttr
4
4
  # Many ActiveAttr modules enhance the behavior of the \#initialize method,
5
5
  # and in doing so, these methods need to accept arguments. However, Ruby's
6
6
  # Object and BasicObject classes, in most versions of Ruby, do not allow any
7
- # arguments to be passed in. This module halts the propgation of
8
- # initialization arguments before invoking the Object classes'
7
+ # arguments to be passed in. This module halts the propagation of
8
+ # initialization arguments before invoking the Object class'
9
9
  # initialization.
10
10
  #
11
11
  # In order to still allow a subclass mixing in this module (directly or
@@ -25,34 +25,24 @@ module ActiveAttr
25
25
  attr_reader :attribute_name, :default_value
26
26
  private :attribute_name, :default_value
27
27
 
28
- # Specify that the attribute should have the given default value
29
- #
30
- # @example Person's first name should default to John
31
- # describe Person do
32
- # it do
33
- # should have_attribute(:first_name).with_default_value_of("John")
34
- # end
35
- # end
36
- #
37
- # @param [Object]
38
- #
39
- # @since 0.5.0
40
- def with_default_value_of(default_value)
41
- @default_value_set = true
42
- @default_value = default_value
43
- self
44
- end
45
-
46
28
  # @return [String] Description
47
29
  # @private
48
30
  def description
49
- "have attribute named #{attribute_name}#{ " with a default value of #{default_value.inspect}" if @default_value_set}"
31
+ "has #{attribute_description}"
50
32
  end
51
33
 
52
34
  # @return [String] Failure message
53
35
  # @private
54
36
  def failure_message
55
- "Expected #{@model_class} to #{description}"
37
+ if !includes_attributes?
38
+ "expected #{@model_class.name} to include ActiveAttr::Attributes"
39
+ elsif !includes_defaults?
40
+ "expected #{@model_class.name} to include ActiveAttr::AttributeDefaults"
41
+ elsif !includes_typecasting?
42
+ "expected #{@model_class.name} to include ActiveAttr::TypecastedAttributes"
43
+ else
44
+ "expected #{@model_class.name} to have #{attribute_description}"
45
+ end
56
46
  end
57
47
 
58
48
  # @param [Symbol, String, #to_sym] attribute_name
@@ -61,27 +51,99 @@ module ActiveAttr
61
51
  raise TypeError, "can't convert #{attribute_name.class} into Symbol" unless attribute_name.respond_to? :to_sym
62
52
  @attribute_name = attribute_name.to_sym
63
53
  @default_value_set = false
54
+ @type = nil
55
+ end
56
+
57
+ # Specify that the attribute should have the given type
58
+ #
59
+ # @example Person's first name should be a String
60
+ # describe Person do
61
+ # it { should have_attribute(:first_name).of_type(String) }
62
+ # end
63
+ #
64
+ # @param [Class] type The expected type
65
+ #
66
+ # @return [HaveAttributeMatcher] The matcher
67
+ #
68
+ # @since 0.5.0
69
+ def of_type(type)
70
+ @type = type
71
+ self
64
72
  end
65
73
 
66
74
  # @private
67
75
  def matches?(model_or_model_class)
68
76
  @model_class = class_from(model_or_model_class)
77
+
78
+ return false if !includes_attributes? || !includes_defaults? || !includes_typecasting?
79
+
69
80
  @attribute_definition = @model_class.attributes[attribute_name]
70
81
 
71
- !!(@attribute_definition && (!@default_value_set || @attribute_definition[:default] == default_value))
82
+ !!(@attribute_definition && default_matches? && type_matches?)
72
83
  end
73
84
 
74
85
  # @return [String] Negative failure message
75
86
  # @private
76
87
  def negative_failure_message
77
- "Expected #{@model_class} to not #{description}"
88
+ "expected #{@model_class.name} to not have #{attribute_description}"
89
+ end
90
+
91
+ # Specify that the attribute should have the given default value
92
+ #
93
+ # @example Person's first name should default to John
94
+ # describe Person do
95
+ # it do
96
+ # should have_attribute(:first_name).with_default_value_of("John")
97
+ # end
98
+ # end
99
+ #
100
+ # @param [Object] default_value The expected default value
101
+ #
102
+ # @return [HaveAttributeMatcher] The matcher
103
+ #
104
+ # @since 0.5.0
105
+ def with_default_value_of(default_value)
106
+ @default_value_set = true
107
+ @default_value = default_value
108
+ self
78
109
  end
79
110
 
80
111
  private
81
112
 
113
+ def attribute_description
114
+ "attribute named #{attribute_name}".tap do |result|
115
+ result << " of type #{@type}" if @type
116
+ result << " with a default value of #{default_value.inspect}" if @default_value_set
117
+ end
118
+ end
119
+
120
+ def includes_attributes?
121
+ model_ancestor_names.include?("ActiveAttr::Attributes")
122
+ end
123
+
124
+ def includes_defaults?
125
+ !@default_value_set || model_ancestor_names.include?("ActiveAttr::AttributeDefaults")
126
+ end
127
+
128
+ def includes_typecasting?
129
+ !@type || model_ancestor_names.include?("ActiveAttr::TypecastedAttributes")
130
+ end
131
+
132
+ def model_ancestor_names
133
+ @model_class.ancestors.map(&:name)
134
+ end
135
+
82
136
  def class_from(object)
83
137
  Class === object ? object : object.class
84
138
  end
139
+
140
+ def default_matches?
141
+ !@default_value_set || @attribute_definition[:default] == default_value
142
+ end
143
+
144
+ def type_matches?
145
+ !@type || @model_class._attribute_type(attribute_name) == @type
146
+ end
85
147
  end
86
148
  end
87
149
  end
@@ -4,6 +4,7 @@ require "active_attr/block_initialization"
4
4
  require "active_attr/logger"
5
5
  require "active_attr/mass_assignment_security"
6
6
  require "active_attr/query_attributes"
7
+ require "active_attr/typecasted_attributes"
7
8
  require "active_model"
8
9
  require "active_support/concern"
9
10
 
@@ -27,6 +28,7 @@ module ActiveAttr
27
28
  include MassAssignmentSecurity
28
29
  include AttributeDefaults
29
30
  include QueryAttributes
31
+ include TypecastedAttributes
30
32
 
31
33
  if defined? ActiveModel::Serializable
32
34
  include ActiveModel::Serializable::JSON
@@ -29,8 +29,7 @@ module ActiveAttr
29
29
 
30
30
  # Test the presence of an attribute
31
31
  #
32
- # Similar to an ActiveRecord model, when the attribute is a zero value or
33
- # is a string that represents false, the method returns false.
32
+ # See {Typecasting::BooleanTypecaster#call} for more details.
34
33
  #
35
34
  # @example Query an attribute
36
35
  # person.query_attribute(:name)
@@ -3,20 +3,26 @@ require "active_attr/typecasting"
3
3
  require "active_support/concern"
4
4
 
5
5
  module ActiveAttr
6
- # TypecastedAttributes enhances attribute handling with typecasting
6
+ # TypecastedAttributes allows types to be declared for your attributes
7
+ #
8
+ # Types are declared by passing the :type option to the attribute class
9
+ # method. After a type is declared, attribute readers will convert any
10
+ # assigned attribute value to the declared type. If the assigned value
11
+ # cannot be cast, nil will be returned instead. You can access the original
12
+ # assigned value using the before_type_cast methods.
13
+ #
14
+ # See {Typecasting} for the currently supported types.
7
15
  #
8
16
  # @example Usage
9
17
  # class Person
10
18
  # include ActiveAttr::TypecastedAttributes
11
- # attribute :name
12
19
  # attribute :age, :type => Integer
13
20
  # end
14
21
  #
15
22
  # person = Person.new
16
- # person.name = "Ben Poweski"
17
- # person.age = "29"
18
- #
23
+ # person.age = "29"
19
24
  # person.age #=> 29
25
+ # person.age_before_type_cast #=> "29"
20
26
  #
21
27
  # @since 0.5.0
22
28
  module TypecastedAttributes
@@ -28,7 +34,17 @@ module ActiveAttr
28
34
  attribute_method_suffix "_before_type_cast"
29
35
  end
30
36
 
31
- # TODO Documentation
37
+ # Read the raw attribute value
38
+ #
39
+ # @example Reading a raw age value
40
+ # person.age = "29"
41
+ # person.attribute_before_type_cast(:age) #=> "29"
42
+ #
43
+ # @param [String, Symbol, #to_s] name Attribute name
44
+ #
45
+ # @return [Object, nil] The attribute value before typecasting
46
+ #
47
+ # @since 0.5.0
32
48
  def attribute_before_type_cast(name)
33
49
  @attributes[name.to_s]
34
50
  end
@@ -1,3 +1,4 @@
1
+ require "active_attr/typecasting/big_decimal_typecaster"
1
2
  require "active_attr/typecasting/boolean"
2
3
  require "active_attr/typecasting/boolean_typecaster"
3
4
  require "active_attr/typecasting/date_time_typecaster"
@@ -11,19 +12,30 @@ require "active_support/concern"
11
12
  module ActiveAttr
12
13
  # Typecasting provides methods to typecast a value to a different type
13
14
  #
15
+ # The following types are supported for typecasting:
16
+ # * BigDecimal
17
+ # * Boolean
18
+ # * Date
19
+ # * DateTime
20
+ # * Float
21
+ # * Integer
22
+ # * Object
23
+ # * String
24
+ #
14
25
  # @since 0.5.0
15
26
  module Typecasting
16
27
  extend ActiveSupport::Concern
17
28
 
18
29
  # @private
19
30
  TYPECASTERS = {
20
- Boolean => BooleanTypecaster,
21
- Date => DateTypecaster,
22
- DateTime => DateTimeTypecaster,
23
- Float => FloatTypecaster,
24
- Integer => IntegerTypecaster,
25
- Object => ObjectTypecaster,
26
- String => StringTypecaster,
31
+ BigDecimal => BigDecimalTypecaster,
32
+ Boolean => BooleanTypecaster,
33
+ Date => DateTypecaster,
34
+ DateTime => DateTimeTypecaster,
35
+ Float => FloatTypecaster,
36
+ Integer => IntegerTypecaster,
37
+ Object => ObjectTypecaster,
38
+ String => StringTypecaster,
27
39
  }
28
40
 
29
41
  # Typecasts a value using a Class