active_attr 0.5.1 → 0.6.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.

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.2-p290@active_attr"
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
@@ -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 "http://rubygems.org"
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", :platforms => :mri_18
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
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env rake
2
+
1
3
  require "bundler/setup"
2
4
  require "bundler/gem_tasks"
3
5
  require "rspec/core/rake_task"
@@ -1,29 +1,26 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "active_attr/version"
2
+ require File.expand_path("../lib/active_attr/version", __FILE__)
4
3
 
5
- Gem::Specification.new do |s|
6
- s.name = "active_attr"
7
- s.version = ActiveAttr::VERSION
8
- s.authors = ["Chris Griego", "Ben Poweski"]
9
- s.email = ["cgriego@gmail.com", "bpoweski@gmail.com"]
10
- s.homepage = "https://github.com/cgriego/active_attr"
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
- s.rubyforge_project = "active_attr"
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
- s.files = `git ls-files`.split("\n")
17
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
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
- s.add_runtime_dependency "activemodel", ">= 3.0.2", "< 4.1"
22
- s.add_runtime_dependency "activesupport", ">= 3.0.2", "< 4.1"
23
-
24
- s.add_development_dependency "bundler", "~> 1.0"
25
- s.add_development_dependency "factory_girl", "~> 2.2"
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
@@ -1,6 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec :development_group => :test, :path => ".."
4
+ gem "factory_girl", "< 3.0", :group => :test if RUBY_VERSION < "1.9.2"
4
5
 
5
6
  gem "activemodel", "~> 3.0.2"
6
7
  gem "activesupport", "~> 3.0.2"
@@ -1,6 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec :development_group => :test, :path => ".."
4
+ gem "factory_girl", "< 3.0", :group => :test if RUBY_VERSION < "1.9.2"
4
5
 
5
6
  gem "activemodel", "~> 3.1.0"
6
7
  gem "activesupport", "~> 3.1.0"
@@ -13,6 +13,7 @@ module ActiveAttr
13
13
  autoload :AttributeDefinition
14
14
  autoload :Attributes
15
15
  autoload :BasicModel
16
+ autoload :BlockInitialization
16
17
  autoload :ChainableInitialization
17
18
  autoload :Error
18
19
  autoload :Logger
@@ -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 AttributeDefinition
154
- # instance will be added to result of the attributes class method.
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
- "#{self.name}#{attributes_list}"
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
- deprecated_object_method = DEPRECATED_OBJECT_METHODS.include?(method_name.to_s)
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, :default_value
26
- private :attribute_name, :default_value
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
- @default_value_set = false
54
- @type = nil
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
- @type = type
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
- @default_value_set = true
107
- @default_value = default_value
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
- private
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
78
+ # @return [String] Description
79
+ # @private
80
+ def description
81
+ "has #{@description}"
118
82
  end
119
83
 
120
- def includes_attributes?
121
- model_ancestor_names.include?("ActiveAttr::Attributes")
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
- def includes_defaults?
125
- !@default_value_set || model_ancestor_names.include?("ActiveAttr::AttributeDefaults")
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
- def includes_typecasting?
129
- !@type || model_ancestor_names.include?("ActiveAttr::TypecastedAttributes")
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
- def model_ancestor_names
133
- @model_class.ancestors.map(&:name)
134
- end
106
+ private
135
107
 
136
- def class_from(object)
137
- Class === object ? object : object.class
108
+ def attribute_definition
109
+ @model_class.attributes[attribute_name]
138
110
  end
139
111
 
140
- def default_matches?
141
- !@default_value_set || @attribute_definition[:default] == default_value
142
- end
112
+ def missing_ancestors
113
+ model_ancestor_names = @model_class.ancestors.map(&:name)
143
114
 
144
- def type_matches?
145
- !@type || @model_class._attribute_type(attribute_name) == @type
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