active_attr 0.13.1 → 0.14.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 (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