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 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