active_attr 0.13.1 → 0.14.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 (37) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +0 -17
  4. data/CHANGELOG.md +14 -0
  5. data/Gemfile +4 -5
  6. data/README.md +140 -112
  7. data/active_attr.gemspec +8 -1
  8. data/gemfiles/rails_3_0.gemfile +0 -4
  9. data/gemfiles/rails_3_1.gemfile +0 -4
  10. data/gemfiles/rails_3_2.gemfile +0 -4
  11. data/gemfiles/rails_4_0.gemfile +1 -1
  12. data/gemfiles/rails_4_1.gemfile +1 -1
  13. data/gemfiles/rails_4_2.gemfile +1 -1
  14. data/gemfiles/rails_5_0.gemfile +1 -1
  15. data/gemfiles/rails_5_1.gemfile +1 -1
  16. data/gemfiles/rails_5_2.gemfile +1 -1
  17. data/gemfiles/rails_head.gemfile +1 -1
  18. data/lib/active_attr.rb +1 -1
  19. data/lib/active_attr/attributes.rb +62 -9
  20. data/lib/active_attr/chainable_initialization.rb +2 -6
  21. data/lib/active_attr/logger.rb +1 -1
  22. data/lib/active_attr/mass_assignment.rb +2 -2
  23. data/lib/active_attr/matchers/have_attribute_matcher.rb +1 -1
  24. data/lib/active_attr/model.rb +1 -0
  25. data/lib/active_attr/query_attributes.rb +1 -1
  26. data/lib/active_attr/railtie.rb +5 -0
  27. data/lib/active_attr/typecasting/boolean_typecaster.rb +8 -0
  28. data/lib/active_attr/version.rb +1 -1
  29. data/spec/functional/active_attr/attributes_spec.rb +8 -8
  30. data/spec/functional/active_attr/matchers/have_attribute_matcher_spec.rb +48 -0
  31. data/spec/functional/active_attr/model_spec.rb +47 -0
  32. data/spec/unit/active_attr/attribute_defaults_spec.rb +8 -0
  33. data/spec/unit/active_attr/attributes_spec.rb +91 -0
  34. data/spec/unit/active_attr/typecasting/big_decimal_typecaster_spec.rb +4 -0
  35. data/spec/unit/active_attr/typecasting/boolean_typecaster_spec.rb +3 -3
  36. metadata +27 -13
  37. data/.ruby-version +0 -1
data/active_attr.gemspec CHANGED
@@ -9,6 +9,10 @@ Gem::Specification.new do |gem|
9
9
  gem.homepage = "https://github.com/cgriego/active_attr"
10
10
  gem.license = "MIT"
11
11
 
12
+ gem.metadata = {
13
+ "changelog_uri" => "https://github.com/cgriego/active_attr/blob/master/CHANGELOG.md",
14
+ }
15
+
12
16
  gem.files = `git ls-files`.split($\)
13
17
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
@@ -16,11 +20,14 @@ Gem::Specification.new do |gem|
16
20
  gem.require_paths = ["lib"]
17
21
  gem.version = ActiveAttr::VERSION
18
22
 
23
+ gem.required_ruby_version = ">= 1.9.2"
24
+
25
+ gem.add_runtime_dependency "actionpack", ">= 3.0.2", "< 6.1"
19
26
  gem.add_runtime_dependency "activemodel", ">= 3.0.2", "< 6.1"
20
27
  gem.add_runtime_dependency "activesupport", ">= 3.0.2", "< 6.1"
21
28
 
22
29
  gem.add_development_dependency "bundler", "~> 1.0"
23
- gem.add_development_dependency "factory_girl", ">= 2.2", "< 5.0"
30
+ gem.add_development_dependency "factory_bot", "< 5.0"
24
31
  gem.add_development_dependency "minitest"
25
32
  gem.add_development_dependency "rake", ">= 0.9.0", "< 10.6"
26
33
  gem.add_development_dependency "rspec", "~> 3.0"
@@ -2,9 +2,5 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec :development_group => :test, :path => ".."
4
4
 
5
- if RUBY_VERSION < "1.9.2"
6
- gem "factory_girl", "< 3.0", :group => :test
7
- end
8
-
9
5
  gem "activemodel", "~> 3.0.2"
10
6
  gem "activesupport", "~> 3.0.2"
@@ -2,10 +2,6 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec :development_group => :test, :path => ".."
4
4
 
5
- if RUBY_VERSION < "1.9.2"
6
- gem "factory_girl", "< 3.0", :group => :test
7
- end
8
-
9
5
  if RUBY_VERSION < "1.9.3"
10
6
  gem "i18n", "< 0.7"
11
7
  end
@@ -2,10 +2,6 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec :development_group => :test, :path => ".."
4
4
 
5
- if RUBY_VERSION < "1.9.2"
6
- gem "factory_girl", "< 3.0", :group => :test
7
- end
8
-
9
5
  if RUBY_VERSION < "1.9.3"
10
6
  gem "i18n", "< 0.7"
11
7
  end
@@ -4,5 +4,5 @@ gemspec :development_group => :test, :path => ".."
4
4
 
5
5
  gem "activemodel", "~> 4.0.0"
6
6
  gem "activesupport", "~> 4.0.0"
7
- gem "actionpack", "~> 4.0.0", :group => :test
7
+ gem "actionpack", "~> 4.0.0"
8
8
  gem "protected_attributes", :group => :test
@@ -4,5 +4,5 @@ gemspec :development_group => :test, :path => ".."
4
4
 
5
5
  gem "activemodel", "~> 4.1.0"
6
6
  gem "activesupport", "~> 4.1.0"
7
- gem "actionpack", "~> 4.1.0", :group => :test
7
+ gem "actionpack", "~> 4.1.0"
8
8
  gem "protected_attributes", :group => :test
@@ -4,5 +4,5 @@ gemspec :development_group => :test, :path => ".."
4
4
 
5
5
  gem "activemodel", "~> 4.2.0"
6
6
  gem "activesupport", "~> 4.2.0"
7
- gem "actionpack", "~> 4.2.0", :group => :test
7
+ gem "actionpack", "~> 4.2.0"
8
8
  gem "protected_attributes", :group => :test
@@ -4,6 +4,6 @@ gemspec :development_group => :test, :path => ".."
4
4
 
5
5
  gem "activemodel", "~> 5.0.0"
6
6
  gem "activesupport", "~> 5.0.0"
7
- gem "actionpack", "~> 5.0.0", :group => :test
7
+ gem "actionpack", "~> 5.0.0"
8
8
  gem "activemodel-serializers-xml", :group => :test
9
9
  gem "protected_attributes_continued", :group => :test
@@ -4,6 +4,6 @@ gemspec :development_group => :test, :path => ".."
4
4
 
5
5
  gem "activemodel", "~> 5.1.0"
6
6
  gem "activesupport", "~> 5.1.0"
7
- gem "actionpack", "~> 5.1.0", :group => :test
7
+ gem "actionpack", "~> 5.1.0"
8
8
  gem "activemodel-serializers-xml", :group => :test
9
9
  gem "protected_attributes_continued", :group => :test
@@ -4,6 +4,6 @@ gemspec :development_group => :test, :path => ".."
4
4
 
5
5
  gem "activemodel", "~> 5.2.0"
6
6
  gem "activesupport", "~> 5.2.0"
7
- gem "actionpack", "~> 5.2.0", :group => :test
7
+ gem "actionpack", "~> 5.2.0"
8
8
  gem "activemodel-serializers-xml", :group => :test
9
9
  gem "protected_attributes_continued", :group => :test
@@ -5,7 +5,7 @@ gemspec :development_group => :test, :path => ".."
5
5
  git "git://github.com/rails/rails.git" do
6
6
  gem "activemodel"
7
7
  gem "activesupport"
8
- gem "actionpack", :group => :test
8
+ gem "actionpack"
9
9
  end
10
10
 
11
11
  gem "activemodel-serializers-xml", :group => :test, :git => "git://github.com/rails/activemodel-serializers-xml.git"
data/lib/active_attr.rb CHANGED
@@ -4,7 +4,7 @@ require "active_support/dependencies/autoload"
4
4
  # ActiveAttr is a set of modules to enhance Plain Old Ruby Objects (POROs)
5
5
  #
6
6
  # These modules give your objects the type of features that are normally found
7
- # in popular Object Relation Mappers (ORMs) like ActiveRecord, DataMapper, and
7
+ # in popular Object Relation Mappers (ORMs) like ActiveRecord, Sequel, and
8
8
  # Mongoid. The goal is to lower the bar for creating easy-to-use Ruby models.
9
9
  module ActiveAttr
10
10
  extend ActiveSupport::Autoload
@@ -5,6 +5,12 @@ require "active_model"
5
5
  require "active_support/concern"
6
6
  require "active_support/hash_with_indifferent_access"
7
7
 
8
+ begin
9
+ require "active_support/parameter_filter"
10
+ rescue LoadError
11
+ require "action_dispatch/http/parameter_filter"
12
+ end
13
+
8
14
  module ActiveAttr
9
15
  # Attributes provides a set of class methods for defining an attributes
10
16
  # schema and instance methods for reading and writing attributes.
@@ -23,11 +29,42 @@ module ActiveAttr
23
29
  extend ActiveSupport::Concern
24
30
  include ActiveModel::AttributeMethods
25
31
 
26
- # Methods deprecated on the Object class which can be safely overridden
27
- # @since 0.3.0
28
- DEPRECATED_OBJECT_METHODS = %w(id type)
32
+ # @private
33
+ # @since 0.14.0
34
+ FILTERED = '[FILTERED]'.freeze
35
+
36
+ # @private
37
+ # @since 0.14.0
38
+ PARAMETER_FILTER = if defined?(ActiveSupport::ParameterFilter)
39
+ ActiveSupport::ParameterFilter
40
+ else
41
+ ActionDispatch::Http::ParameterFilter
42
+ end
43
+
44
+ # Specifies attributes which won't be exposed while calling #inspect
45
+ #
46
+ # @return [Array<#to_s, Regexp, Proc>] filter_attributes Configured
47
+ # global default filtered attributes
48
+ #
49
+ # @since 0.14.0
50
+ def self.filter_attributes
51
+ @filter_attributes ||= []
52
+ end
53
+
54
+ # Configure the global default filtered attributes
55
+ #
56
+ # @param [Array<#to_s, Regexp, Proc>] new_filter_attributes The new
57
+ # global default filtered attributes
58
+ #
59
+ # @since 0.14.0
60
+ def self.filter_attributes=(new_filter_attributes)
61
+ @filter_attributes = new_filter_attributes
62
+ end
29
63
 
30
64
  included do
65
+ class_attribute :filter_attributes, :instance_writer => false
66
+ self.filter_attributes = Attributes.filter_attributes
67
+
31
68
  attribute_method_suffix "" if attribute_method_matchers.none? { |matcher| matcher.prefix == "" && matcher.suffix == "" }
32
69
  attribute_method_suffix "="
33
70
  end
@@ -70,7 +107,20 @@ module ActiveAttr
70
107
  #
71
108
  # @since 0.2.0
72
109
  def inspect
73
- attribute_descriptions = attributes.sort.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
110
+ inspection_filter = PARAMETER_FILTER.new(filter_attributes)
111
+ original_attributes = attributes
112
+ filtered_attributes = inspection_filter.filter(original_attributes)
113
+
114
+ attribute_descriptions = filtered_attributes.sort.map { |key, value|
115
+ inspect_value = case
116
+ when original_attributes[key].nil? then nil.inspect
117
+ when value == FILTERED then FILTERED
118
+ else value.inspect
119
+ end
120
+
121
+ "#{key}: #{inspect_value}"
122
+ }.join(", ")
123
+
74
124
  separator = " " unless attribute_descriptions.empty?
75
125
  "#<#{self.class.name}#{separator}#{attribute_descriptions}>"
76
126
  end
@@ -150,7 +200,9 @@ module ActiveAttr
150
200
  #
151
201
  # @since 0.7.0
152
202
  def attributes_map
153
- Hash[ self.class.attribute_names.map { |name| [name, yield(name)] } ]
203
+ self.class.attribute_names.each_with_object({}) do |name, hash|
204
+ hash[name] = yield(name)
205
+ end
154
206
  end
155
207
 
156
208
  module ClassMethods
@@ -202,6 +254,7 @@ module ActiveAttr
202
254
  # Force active model to generate attribute methods
203
255
  remove_instance_variable("@attribute_methods_generated") if instance_variable_defined?("@attribute_methods_generated")
204
256
  define_attribute_methods([attribute_definition.name]) unless attribute_names.include? attribute_name
257
+ remove_instance_variable("@attribute_names") if instance_variable_defined?("@attribute_names")
205
258
  attributes[attribute_name] = attribute_definition
206
259
  end
207
260
  end
@@ -215,7 +268,7 @@ module ActiveAttr
215
268
  #
216
269
  # @since 0.5.0
217
270
  def attribute_names
218
- attributes.keys
271
+ @attribute_names ||= attributes.keys
219
272
  end
220
273
 
221
274
  # Returns a Hash of AttributeDefinition instances
@@ -251,7 +304,7 @@ module ActiveAttr
251
304
  # @since 0.6.0
252
305
  def dangerous_attribute?(name)
253
306
  attribute_methods(name).detect do |method_name|
254
- !DEPRECATED_OBJECT_METHODS.include?(method_name.to_s) && allocate.respond_to?(method_name, true)
307
+ allocate.respond_to?(method_name, true)
255
308
  end unless attribute_names.include? name.to_s
256
309
  end
257
310
 
@@ -273,8 +326,8 @@ module ActiveAttr
273
326
 
274
327
  # Assign a set of attribute definitions, used when subclassing models
275
328
  #
276
- # @param [Array<ActiveAttr::AttributeDefinition>] The Array of
277
- # AttributeDefinition instances
329
+ # @param [Array<ActiveAttr::AttributeDefinition>] attributes The
330
+ # Array of AttributeDefinition instances
278
331
  #
279
332
  # @since 0.2.2
280
333
  def attributes=(attributes)
@@ -18,14 +18,10 @@ module ActiveAttr
18
18
  module ChainableInitialization
19
19
  class << self
20
20
  # A collection of Ruby base objects
21
- # [Object] on Ruby 1.8
22
- # [Object, BasicObject] on Ruby 1.9
21
+ # [Object, BasicObject]
23
22
  #
24
23
  # @private
25
- BASE_OBJECTS = [].tap do |base_objects|
26
- superclass = Class.new
27
- base_objects << superclass while superclass = superclass.superclass
28
- end
24
+ BASE_OBJECTS = [Object, BasicObject]
29
25
 
30
26
  # Only append the features of this module to the class that inherits
31
27
  # directly from one of the BASE_OBJECTS
@@ -24,7 +24,7 @@ module ActiveAttr
24
24
 
25
25
  # Configure the global default logger
26
26
  #
27
- # @param [Logger, #debug] logger The new global default logger
27
+ # @param [Logger, #debug] new_logger The new global default logger
28
28
  #
29
29
  # @since 0.3.0
30
30
  def self.logger=(new_logger)
@@ -24,8 +24,8 @@ module ActiveAttr
24
24
  # person.first_name #=> "Chris"
25
25
  # person.last_name #=> "Griego"
26
26
  #
27
- # @param [Hash{#to_s => Object}, #each] attributes Attributes used to
28
- # populate the model
27
+ # @param [Hash{#to_s => Object}, #each] new_attributes Attributes
28
+ # used to populate the model
29
29
  # @param [Hash, #[]] options Options that affect mass assignment
30
30
  #
31
31
  # @option options [Symbol] :as (:default) Mass assignment role
@@ -76,7 +76,7 @@ module ActiveAttr
76
76
  @attribute_options[:default] = default_value
77
77
  @description << " with a default value of #{default_value.inspect}"
78
78
  @expected_ancestors << "ActiveAttr::AttributeDefaults"
79
- @attribute_expectations << lambda { actual_attribute_definition[:default] == default_value }
79
+ @attribute_expectations << lambda { @model_class.allocate.send(:_attribute_default, @attribute_name) == default_value }
80
80
  self
81
81
  end
82
82
 
@@ -23,6 +23,7 @@ module ActiveAttr
23
23
  module Model
24
24
  extend ActiveSupport::Concern
25
25
  include BasicModel
26
+ include ActiveModel::Validations::Callbacks
26
27
  include BlockInitialization
27
28
  include Logger
28
29
  include MassAssignment
@@ -52,7 +52,7 @@ module ActiveAttr
52
52
  private
53
53
 
54
54
  def attribute?(name)
55
- Typecasting::BooleanTypecaster.new.call(read_attribute(name))
55
+ !!Typecasting::BooleanTypecaster.new.call(read_attribute(name))
56
56
  end
57
57
  end
58
58
  end
@@ -1,3 +1,4 @@
1
+ require "active_attr/attributes"
1
2
  require "active_attr/logger"
2
3
 
3
4
  module ActiveAttr
@@ -6,5 +7,9 @@ module ActiveAttr
6
7
  initializer "active_attr.logger" do
7
8
  Logger.logger ||= ::Rails.logger
8
9
  end
10
+
11
+ initializer "active_attr.attributes" do
12
+ Attributes.filter_attributes += Rails.application.config.filter_parameters
13
+ end
9
14
  end
10
15
  end
@@ -17,6 +17,13 @@ module ActiveAttr
17
17
  # @since 0.5.0
18
18
  FALSE_VALUES = ["n", "N", "no", "No", "NO", "false", "False", "FALSE", "off", "Off", "OFF", "f", "F"]
19
19
 
20
+ # Values which force a nil result for typecasting
21
+ #
22
+ # These values are based on the behavior of ActiveRecord
23
+ #
24
+ # @since 0.14.0
25
+ NIL_VALUES = ["", nil]
26
+
20
27
  # Typecasts an object to true or false
21
28
  #
22
29
  # Similar to ActiveRecord, when the attribute is a zero value or
@@ -34,6 +41,7 @@ module ActiveAttr
34
41
  def call(value)
35
42
  case value
36
43
  when *FALSE_VALUES then false
44
+ when *NIL_VALUES then nil
37
45
  when Numeric, /\A[-+]?(0+\.?0*|0*\.?0+)\z/ then !value.to_f.zero?
38
46
  else value.present?
39
47
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveAttr
2
2
  # Complete version string
3
3
  # @since 0.1.0
4
- VERSION = "0.13.1"
4
+ VERSION = "0.14.0"
5
5
  end
@@ -1,7 +1,7 @@
1
1
  require "spec_helper"
2
2
  require "active_attr/attributes"
3
3
  require "active_model"
4
- require "factory_girl"
4
+ require "factory_bot"
5
5
 
6
6
  module ActiveAttr
7
7
  describe Attributes do
@@ -149,22 +149,22 @@ module ActiveAttr
149
149
  end
150
150
  end
151
151
 
152
- context "building with FactoryGirl" do
153
- subject(:model) { FactoryGirl.build(:person) }
152
+ context "building with FactoryBot" do
153
+ subject(:model) { FactoryBot.build(:person) }
154
154
 
155
155
  before do
156
156
  Object.const_set("Person", model_class)
157
157
 
158
- FactoryGirl.define do
158
+ FactoryBot.define do
159
159
  factory :person, :class => :person do
160
- first_name "Chris"
161
- last_name "Griego"
160
+ first_name { "Chris" }
161
+ last_name { "Griego" }
162
162
  end
163
163
  end
164
164
  end
165
165
 
166
166
  after do
167
- FactoryGirl.factories.clear
167
+ FactoryBot.factories.clear
168
168
  Object.send :remove_const, "Person"
169
169
  end
170
170
 
@@ -284,7 +284,7 @@ module ActiveAttr
284
284
  end
285
285
 
286
286
  def respond_to?(method_name, include_private=false)
287
- super || method_name.to_s == "my_less_proper_missing_method" || (RUBY_VERSION < "1.9" && respond_to_missing?(method_name, include_private))
287
+ super || method_name.to_s == "my_less_proper_missing_method"
288
288
  end
289
289
  end
290
290
  end
@@ -14,6 +14,14 @@ module ActiveAttr
14
14
  include AttributeDefaults
15
15
  include TypecastedAttributes
16
16
 
17
+ def john
18
+ "John"
19
+ end
20
+
21
+ def doe
22
+ "Doe"
23
+ end
24
+
17
25
  def self.name
18
26
  "Person"
19
27
  end
@@ -167,6 +175,25 @@ module ActiveAttr
167
175
  end
168
176
  end
169
177
 
178
+ context "a class with the attribute and a different default (lazy evaluation)" do
179
+ before { model_class.attribute :first_name, :default => lambda { doe } }
180
+
181
+ describe "#matches?" do
182
+ it { matcher.matches?(model_class).should == false }
183
+ end
184
+
185
+ describe "#failure_message" do
186
+ before { matcher.matches?(model_class) }
187
+
188
+ it do
189
+ matcher.failure_message.should match Regexp.new <<-MESSAGE.strip_heredoc.chomp, Regexp::MULTILINE
190
+ expected: attribute :first_name, :default => "John"
191
+ got: attribute :first_name, :default => #<Proc.+?>
192
+ MESSAGE
193
+ end
194
+ end
195
+ end
196
+
170
197
  context "a class with the attribute and the right default" do
171
198
  before { model_class.attribute :first_name, :default => "John" }
172
199
 
@@ -187,6 +214,27 @@ module ActiveAttr
187
214
  end
188
215
  end
189
216
  end
217
+
218
+ context "a class with the attribute and the right default (lazy evaluation)" do
219
+ before { model_class.attribute :first_name, :default => lambda { john } }
220
+
221
+ describe "#matches?" do
222
+ it { matcher.matches?(model_class).should == true }
223
+ end
224
+
225
+ [:negative_failure_message, :failure_message_when_negated].each do |method|
226
+ describe "##{method}" do
227
+ before { matcher.matches?(model_class) }
228
+
229
+ it do
230
+ matcher.send(method).should match Regexp.new <<-MESSAGE.strip_heredoc.chomp, Regexp::MULTILINE
231
+ expected not: attribute :first_name, :default => "John"
232
+ got: attribute :first_name, :default => #<Proc.+?>
233
+ MESSAGE
234
+ end
235
+ end
236
+ end
237
+ end
190
238
  end
191
239
 
192
240
  context "a matcher with a default value of false" do