active_attr 0.5.1 → 0.6.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/.rvmrc +1 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile +4 -3
- data/LICENSE +22 -0
- data/Rakefile +2 -0
- data/active_attr.gemspec +20 -23
- data/gemfiles/rails_3_0.gemfile +1 -0
- data/gemfiles/rails_3_1.gemfile +1 -0
- data/lib/active_attr.rb +1 -0
- data/lib/active_attr/attribute_definition.rb +16 -0
- data/lib/active_attr/attributes.rb +63 -17
- data/lib/active_attr/matchers/have_attribute_matcher.rb +39 -67
- data/lib/active_attr/typecasted_attributes.rb +11 -2
- data/lib/active_attr/typecasting.rb +21 -29
- data/lib/active_attr/typecasting/unknown_typecaster_error.rb +13 -0
- data/lib/active_attr/version.rb +1 -1
- data/spec/functional/active_attr/attribute_defaults_spec.rb +9 -1
- data/spec/functional/active_attr/attributes_spec.rb +72 -24
- data/spec/functional/active_attr/typecasted_attributes_spec.rb +22 -0
- data/spec/support/age.rb +17 -0
- data/spec/unit/active_attr/attribute_definition_spec.rb +24 -0
- data/spec/unit/active_attr/attributes_spec.rb +73 -37
- data/spec/unit/active_attr/typecasting/unknown_typecaster_error_spec.rb +11 -0
- data/spec/unit/active_attr/typecasting_spec.rb +22 -50
- metadata +74 -23
- data/MIT-LICENSE +0 -18
data/.rvmrc
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
# development environment upon cd'ing into the directory
|
5
5
|
|
6
6
|
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
7
|
-
environment_id="ruby-1.9.
|
7
|
+
environment_id="ruby-1.9.3@active_attr"
|
8
8
|
|
9
9
|
#
|
10
10
|
# Uncomment the following lines if you want to verify rvm version per project
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# ActiveAttr 0.6.0 (June 27, 2012) #
|
2
|
+
|
3
|
+
* Added AttributeDefinition#inspect
|
4
|
+
* Added Attributes.attribute!
|
5
|
+
* Added Attributes.dangerous_attribute?
|
6
|
+
* Added Typecasting#typecaster_for
|
7
|
+
* Added Typecasting::UnknownTypecasterError
|
8
|
+
* Changed Typecasting#typecast_attribute to take a typecaster, not a type
|
9
|
+
* Removed Typecasting#typecast_value
|
10
|
+
* TypecastedAttributes now supports a :typecaster option on attribute
|
11
|
+
definitions which can be any object that responds to #call
|
12
|
+
|
1
13
|
# ActiveAttr 0.5.1 (March 16, 2012) #
|
2
14
|
|
3
15
|
* ActiveAttr now supports Rails 3.0.2+ (Egor Baranov)
|
data/Gemfile
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
source "
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
3
|
gemspec :development_group => :test
|
4
|
+
gem "factory_girl", "< 3.0", :group => :test if RUBY_VERSION < "1.9.2"
|
4
5
|
|
5
6
|
group :development do
|
7
|
+
gem "debugger", :platforms => :mri_19
|
6
8
|
gem "growl"
|
7
9
|
gem "guard"
|
8
10
|
gem "guard-bundler"
|
@@ -10,8 +12,7 @@ group :development do
|
|
10
12
|
gem "rb-fsevent"
|
11
13
|
gem "rdiscount"
|
12
14
|
gem "rdoc"
|
13
|
-
gem "ruby-debug",
|
14
|
-
gem "ruby-debug19", :platforms => :mri_19 if RUBY_VERSION < "1.9.3"
|
15
|
+
gem "ruby-debug", :platforms => :mri_18
|
15
16
|
gem "spec_coverage", :platforms => :mri_19
|
16
17
|
gem "travis-lint"
|
17
18
|
gem "yard"
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (C) 2011-2012 by Chris Griego, Ben Poweski
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
data/active_attr.gemspec
CHANGED
@@ -1,29 +1,26 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require "active_attr/version"
|
2
|
+
require File.expand_path("../lib/active_attr/version", __FILE__)
|
4
3
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
s.summary = %q{What ActiveModel left out}
|
12
|
-
s.description = %q{Create plain old ruby models without reinventing the wheel.}
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Chris Griego", "Ben Poweski"]
|
6
|
+
gem.email = ["cgriego@gmail.com", "bpoweski@gmail.com"]
|
7
|
+
gem.description = %q{Create plain old ruby models without reinventing the wheel.}
|
8
|
+
gem.summary = %q{What ActiveModel left out}
|
9
|
+
gem.homepage = "https://github.com/cgriego/active_attr"
|
13
10
|
|
14
|
-
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "active_attr"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = ActiveAttr::VERSION
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
-
s.require_paths = ["lib"]
|
18
|
+
gem.add_runtime_dependency "activemodel", ">= 3.0.2", "< 4.1"
|
19
|
+
gem.add_runtime_dependency "activesupport", ">= 3.0.2", "< 4.1"
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
s.add_development_dependency "rake", "~> 0.9.0"
|
27
|
-
s.add_development_dependency "rspec", "~> 2.6"
|
28
|
-
s.add_development_dependency "tzinfo", "~> 0.3.29"
|
21
|
+
gem.add_development_dependency "bundler", "~> 1.0"
|
22
|
+
gem.add_development_dependency "factory_girl", ">= 2.2", "< 4.0"
|
23
|
+
gem.add_development_dependency "rake", "~> 0.9.0"
|
24
|
+
gem.add_development_dependency "rspec", "~> 2.6"
|
25
|
+
gem.add_development_dependency "tzinfo", "~> 0.3.29"
|
29
26
|
end
|
data/gemfiles/rails_3_0.gemfile
CHANGED
data/gemfiles/rails_3_1.gemfile
CHANGED
data/lib/active_attr.rb
CHANGED
@@ -47,6 +47,7 @@ module ActiveAttr
|
|
47
47
|
# AttributeDefinition.new(:amount)
|
48
48
|
#
|
49
49
|
# @param [Symbol, String, #to_sym] name attribute name
|
50
|
+
# @param [Hash{Symbol => Object}] options attribute options
|
50
51
|
#
|
51
52
|
# @return [ActiveAttr::AttributeDefinition]
|
52
53
|
#
|
@@ -57,6 +58,21 @@ module ActiveAttr
|
|
57
58
|
@options = options
|
58
59
|
end
|
59
60
|
|
61
|
+
# Returns the code that would generate the attribute definition
|
62
|
+
#
|
63
|
+
# @example Inspect the attribute definition
|
64
|
+
# attribute.inspect
|
65
|
+
#
|
66
|
+
# @return [String] Human-readable presentation of the attribute
|
67
|
+
# definition
|
68
|
+
#
|
69
|
+
# @since 0.6.0
|
70
|
+
def inspect
|
71
|
+
options_description = options.map { |key, value| "#{key.inspect} => #{value.inspect}" }.sort.join(", ")
|
72
|
+
inspected_options = ", #{options_description}" unless options_description.empty?
|
73
|
+
"attribute :#{name}#{inspected_options}"
|
74
|
+
end
|
75
|
+
|
60
76
|
# The attribute name
|
61
77
|
#
|
62
78
|
# @return [String] the attribute name
|
@@ -120,14 +120,6 @@ module ActiveAttr
|
|
120
120
|
end
|
121
121
|
alias_method :[]=, :write_attribute
|
122
122
|
|
123
|
-
protected
|
124
|
-
|
125
|
-
# Overrides ActiveModel::AttributeMethods
|
126
|
-
# @private
|
127
|
-
def attribute_method?(attr_name)
|
128
|
-
self.class.attribute_names.include? attr_name.to_s
|
129
|
-
end
|
130
|
-
|
131
123
|
private
|
132
124
|
|
133
125
|
# Read an attribute from the attributes hash
|
@@ -150,8 +142,9 @@ module ActiveAttr
|
|
150
142
|
# Defines an attribute
|
151
143
|
#
|
152
144
|
# For each attribute that is defined, a getter and setter will be
|
153
|
-
# added as an instance method to the model. An
|
154
|
-
# instance will be added to result of the
|
145
|
+
# added as an instance method to the model. An
|
146
|
+
# {AttributeDefinition} instance will be added to result of the
|
147
|
+
# attributes class method.
|
155
148
|
#
|
156
149
|
# @example Define an attribute.
|
157
150
|
# attribute :name
|
@@ -161,8 +154,34 @@ module ActiveAttr
|
|
161
154
|
# @raise [DangerousAttributeError] if the attribute name conflicts with
|
162
155
|
# existing methods
|
163
156
|
#
|
157
|
+
# @return [AttributeDefinition] Attribute's definition
|
158
|
+
#
|
164
159
|
# @since 0.2.0
|
165
160
|
def attribute(name, options={})
|
161
|
+
if dangerous_attribute_method_name = dangerous_attribute?(name)
|
162
|
+
raise DangerousAttributeError, %{an attribute method named "#{dangerous_attribute_method_name}" would conflict with an existing method}
|
163
|
+
else
|
164
|
+
attribute! name, options
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Defines an attribute without checking for conflicts
|
169
|
+
#
|
170
|
+
# Allows you to define an attribute whose methods will conflict
|
171
|
+
# with an existing method. For example, Ruby's Timeout library
|
172
|
+
# adds a timeout method to Object. Attempting to define a timeout
|
173
|
+
# attribute using .attribute will raise a
|
174
|
+
# {DangerousAttributeError}, but .attribute! will not.
|
175
|
+
#
|
176
|
+
# @example Define a dangerous attribute.
|
177
|
+
# attribute! :timeout
|
178
|
+
#
|
179
|
+
# @param (see AttributeDefinition#initialize)
|
180
|
+
#
|
181
|
+
# @return [AttributeDefinition] Attribute's definition
|
182
|
+
#
|
183
|
+
# @since 0.6.0
|
184
|
+
def attribute!(name, options={})
|
166
185
|
AttributeDefinition.new(name, options).tap do |attribute_definition|
|
167
186
|
attribute_name = attribute_definition.name.to_s
|
168
187
|
# Force active model to generate attribute methods
|
@@ -197,6 +216,30 @@ module ActiveAttr
|
|
197
216
|
@attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
198
217
|
end
|
199
218
|
|
219
|
+
# Determine if a given attribute name is dangerous
|
220
|
+
#
|
221
|
+
# Some attribute names can cause conflicts with existing methods
|
222
|
+
# on an object. For example, an attribute named "timeout" would
|
223
|
+
# conflict with the timeout method that Ruby's Timeout library
|
224
|
+
# mixes into Object.
|
225
|
+
#
|
226
|
+
# @example Testing a harmless attribute
|
227
|
+
# Person.dangerous_attribute? :name #=> false
|
228
|
+
#
|
229
|
+
# @example Testing a dangerous attribute
|
230
|
+
# Person.dangerous_attribute? :nil #=> "nil?"
|
231
|
+
#
|
232
|
+
# @param name Attribute name
|
233
|
+
#
|
234
|
+
# @return [false, String] False or the conflicting method name
|
235
|
+
#
|
236
|
+
# @since 0.6.0
|
237
|
+
def dangerous_attribute?(name)
|
238
|
+
attribute_methods(name).detect do |method_name|
|
239
|
+
!DEPRECATED_OBJECT_METHODS.include?(method_name.to_s) && allocate.respond_to?(method_name, true)
|
240
|
+
end unless attribute_names.include? name.to_s
|
241
|
+
end
|
242
|
+
|
200
243
|
# Returns the class name plus its attribute names
|
201
244
|
#
|
202
245
|
# @example Inspect the model's definition.
|
@@ -208,7 +251,7 @@ module ActiveAttr
|
|
208
251
|
def inspect
|
209
252
|
inspected_attributes = attribute_names.sort
|
210
253
|
attributes_list = "(#{inspected_attributes.join(", ")})" unless inspected_attributes.empty?
|
211
|
-
"#{
|
254
|
+
"#{name}#{attributes_list}"
|
212
255
|
end
|
213
256
|
|
214
257
|
protected
|
@@ -223,17 +266,20 @@ module ActiveAttr
|
|
223
266
|
@attributes = attributes
|
224
267
|
end
|
225
268
|
|
226
|
-
# Overrides ActiveModel::AttributeMethods
|
227
|
-
# @private
|
269
|
+
# Overrides ActiveModel::AttributeMethods to backport 3.2 fix
|
228
270
|
def instance_method_already_implemented?(method_name)
|
229
|
-
|
230
|
-
already_implemented = !deprecated_object_method && self.allocate.respond_to?(method_name, true)
|
231
|
-
raise DangerousAttributeError, %{an attribute method named "#{method_name}" would conflict with an existing method} if already_implemented
|
232
|
-
false
|
271
|
+
generated_attribute_methods.method_defined?(method_name)
|
233
272
|
end
|
234
273
|
|
235
274
|
private
|
236
275
|
|
276
|
+
# Expand an attribute name into its generated methods names
|
277
|
+
#
|
278
|
+
# @since 0.6.0
|
279
|
+
def attribute_methods(name)
|
280
|
+
attribute_method_matchers.map { |matcher| matcher.method_name name }
|
281
|
+
end
|
282
|
+
|
237
283
|
# Ruby inherited hook to assign superclass attributes to subclasses
|
238
284
|
#
|
239
285
|
# @since 0.2.2
|
@@ -22,36 +22,17 @@ module ActiveAttr
|
|
22
22
|
#
|
23
23
|
# @since 0.2.0
|
24
24
|
class HaveAttributeMatcher
|
25
|
-
attr_reader :attribute_name
|
26
|
-
private :attribute_name
|
27
|
-
|
28
|
-
# @return [String] Description
|
29
|
-
# @private
|
30
|
-
def description
|
31
|
-
"has #{attribute_description}"
|
32
|
-
end
|
33
|
-
|
34
|
-
# @return [String] Failure message
|
35
|
-
# @private
|
36
|
-
def failure_message
|
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
|
46
|
-
end
|
25
|
+
attr_reader :attribute_name
|
26
|
+
private :attribute_name
|
47
27
|
|
48
28
|
# @param [Symbol, String, #to_sym] attribute_name
|
49
29
|
# @private
|
50
30
|
def initialize(attribute_name)
|
51
31
|
raise TypeError, "can't convert #{attribute_name.class} into Symbol" unless attribute_name.respond_to? :to_sym
|
52
32
|
@attribute_name = attribute_name.to_sym
|
53
|
-
@
|
54
|
-
@
|
33
|
+
@description = "attribute named #{attribute_name}"
|
34
|
+
@expected_ancestors = ["ActiveAttr::Attributes"]
|
35
|
+
@attribute_expectations = [lambda { attribute_definition }]
|
55
36
|
end
|
56
37
|
|
57
38
|
# Specify that the attribute should have the given type
|
@@ -67,27 +48,12 @@ module ActiveAttr
|
|
67
48
|
#
|
68
49
|
# @since 0.5.0
|
69
50
|
def of_type(type)
|
70
|
-
@
|
51
|
+
@description << " of type #{type}"
|
52
|
+
@expected_ancestors << "ActiveAttr::TypecastedAttributes"
|
53
|
+
@attribute_expectations << lambda { @model_class._attribute_type(attribute_name) == type }
|
71
54
|
self
|
72
55
|
end
|
73
56
|
|
74
|
-
# @private
|
75
|
-
def matches?(model_or_model_class)
|
76
|
-
@model_class = class_from(model_or_model_class)
|
77
|
-
|
78
|
-
return false if !includes_attributes? || !includes_defaults? || !includes_typecasting?
|
79
|
-
|
80
|
-
@attribute_definition = @model_class.attributes[attribute_name]
|
81
|
-
|
82
|
-
!!(@attribute_definition && default_matches? && type_matches?)
|
83
|
-
end
|
84
|
-
|
85
|
-
# @return [String] Negative failure message
|
86
|
-
# @private
|
87
|
-
def negative_failure_message
|
88
|
-
"expected #{@model_class.name} to not have #{attribute_description}"
|
89
|
-
end
|
90
|
-
|
91
57
|
# Specify that the attribute should have the given default value
|
92
58
|
#
|
93
59
|
# @example Person's first name should default to John
|
@@ -103,46 +69,52 @@ module ActiveAttr
|
|
103
69
|
#
|
104
70
|
# @since 0.5.0
|
105
71
|
def with_default_value_of(default_value)
|
106
|
-
@
|
107
|
-
@
|
72
|
+
@description << " with a default value of #{default_value.inspect}"
|
73
|
+
@expected_ancestors << "ActiveAttr::AttributeDefaults"
|
74
|
+
@attribute_expectations << lambda { attribute_definition[:default] == default_value }
|
108
75
|
self
|
109
76
|
end
|
110
77
|
|
111
|
-
|
112
|
-
|
113
|
-
def
|
114
|
-
"
|
115
|
-
result << " of type #{@type}" if @type
|
116
|
-
result << " with a default value of #{default_value.inspect}" if @default_value_set
|
117
|
-
end
|
78
|
+
# @return [String] Description
|
79
|
+
# @private
|
80
|
+
def description
|
81
|
+
"has #{@description}"
|
118
82
|
end
|
119
83
|
|
120
|
-
|
121
|
-
|
84
|
+
# @private
|
85
|
+
def matches?(model_or_model_class)
|
86
|
+
@model_class = Class === model_or_model_class ? model_or_model_class : model_or_model_class.class
|
87
|
+
missing_ancestors.none? && @attribute_expectations.all? { | expectation| expectation.call }
|
122
88
|
end
|
123
89
|
|
124
|
-
|
125
|
-
|
90
|
+
# @return [String] Failure message
|
91
|
+
# @private
|
92
|
+
def failure_message
|
93
|
+
if missing_ancestors.any?
|
94
|
+
"expected #{@model_class.name} to include #{missing_ancestors.first}"
|
95
|
+
else
|
96
|
+
"expected #{@model_class.name} to have #{@description}"
|
97
|
+
end
|
126
98
|
end
|
127
99
|
|
128
|
-
|
129
|
-
|
100
|
+
# @return [String] Negative failure message
|
101
|
+
# @private
|
102
|
+
def negative_failure_message
|
103
|
+
"expected #{@model_class.name} to not have #{@description}"
|
130
104
|
end
|
131
105
|
|
132
|
-
|
133
|
-
@model_class.ancestors.map(&:name)
|
134
|
-
end
|
106
|
+
private
|
135
107
|
|
136
|
-
def
|
137
|
-
|
108
|
+
def attribute_definition
|
109
|
+
@model_class.attributes[attribute_name]
|
138
110
|
end
|
139
111
|
|
140
|
-
def
|
141
|
-
|
142
|
-
end
|
112
|
+
def missing_ancestors
|
113
|
+
model_ancestor_names = @model_class.ancestors.map(&:name)
|
143
114
|
|
144
|
-
|
145
|
-
|
115
|
+
@expected_ancestors.reject do |ancestor_name|
|
116
|
+
model_ancestor_names.include? ancestor_name
|
117
|
+
end
|
146
118
|
end
|
147
119
|
end
|
148
120
|
end
|