activemodel 4.2.11.3 → 5.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activemodel might be problematic. Click here for more details.

Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +84 -93
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +8 -16
  5. data/lib/active_model.rb +3 -2
  6. data/lib/active_model/attribute_assignment.rb +52 -0
  7. data/lib/active_model/attribute_methods.rb +16 -16
  8. data/lib/active_model/callbacks.rb +3 -3
  9. data/lib/active_model/conversion.rb +3 -3
  10. data/lib/active_model/dirty.rb +34 -35
  11. data/lib/active_model/errors.rb +117 -63
  12. data/lib/active_model/forbidden_attributes_protection.rb +3 -2
  13. data/lib/active_model/gem_version.rb +5 -5
  14. data/lib/active_model/lint.rb +32 -28
  15. data/lib/active_model/locale/en.yml +2 -1
  16. data/lib/active_model/model.rb +3 -4
  17. data/lib/active_model/naming.rb +5 -4
  18. data/lib/active_model/secure_password.rb +2 -9
  19. data/lib/active_model/serialization.rb +36 -9
  20. data/lib/active_model/serializers/json.rb +1 -1
  21. data/lib/active_model/type.rb +59 -0
  22. data/lib/active_model/type/big_integer.rb +13 -0
  23. data/lib/active_model/type/binary.rb +50 -0
  24. data/lib/active_model/type/boolean.rb +21 -0
  25. data/lib/active_model/type/date.rb +50 -0
  26. data/lib/active_model/type/date_time.rb +44 -0
  27. data/lib/active_model/type/decimal.rb +52 -0
  28. data/lib/active_model/type/decimal_without_scale.rb +11 -0
  29. data/lib/active_model/type/float.rb +25 -0
  30. data/lib/active_model/type/helpers.rb +4 -0
  31. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +35 -0
  32. data/lib/active_model/type/helpers/mutable.rb +18 -0
  33. data/lib/active_model/type/helpers/numeric.rb +34 -0
  34. data/lib/active_model/type/helpers/time_value.rb +77 -0
  35. data/lib/active_model/type/immutable_string.rb +29 -0
  36. data/lib/active_model/type/integer.rb +66 -0
  37. data/lib/active_model/type/registry.rb +64 -0
  38. data/lib/active_model/type/string.rb +19 -0
  39. data/lib/active_model/type/text.rb +11 -0
  40. data/lib/active_model/type/time.rb +46 -0
  41. data/lib/active_model/type/unsigned_integer.rb +15 -0
  42. data/lib/active_model/type/value.rb +112 -0
  43. data/lib/active_model/validations.rb +35 -3
  44. data/lib/active_model/validations/absence.rb +1 -1
  45. data/lib/active_model/validations/acceptance.rb +61 -9
  46. data/lib/active_model/validations/callbacks.rb +3 -3
  47. data/lib/active_model/validations/confirmation.rb +16 -4
  48. data/lib/active_model/validations/exclusion.rb +3 -1
  49. data/lib/active_model/validations/format.rb +1 -1
  50. data/lib/active_model/validations/helper_methods.rb +13 -0
  51. data/lib/active_model/validations/inclusion.rb +3 -3
  52. data/lib/active_model/validations/length.rb +48 -17
  53. data/lib/active_model/validations/numericality.rb +12 -13
  54. data/lib/active_model/validations/validates.rb +1 -1
  55. data/lib/active_model/validations/with.rb +0 -10
  56. data/lib/active_model/validator.rb +6 -2
  57. data/lib/active_model/version.rb +1 -1
  58. metadata +34 -9
  59. data/lib/active_model/serializers/xml.rb +0 -238
@@ -17,8 +17,9 @@ module ActiveModel
17
17
  module ForbiddenAttributesProtection # :nodoc:
18
18
  protected
19
19
  def sanitize_for_mass_assignment(attributes)
20
- if attributes.respond_to?(:permitted?) && !attributes.permitted?
21
- raise ActiveModel::ForbiddenAttributesError
20
+ if attributes.respond_to?(:permitted?)
21
+ raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
22
+ attributes.to_h
22
23
  else
23
24
  attributes
24
25
  end
@@ -1,14 +1,14 @@
1
1
  module ActiveModel
2
- # Returns the version of the currently loaded Active Model as a <tt>Gem::Version</tt>
2
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
3
3
  def self.gem_version
4
4
  Gem::Version.new VERSION::STRING
5
5
  end
6
6
 
7
7
  module VERSION
8
- MAJOR = 4
9
- MINOR = 2
10
- TINY = 11
11
- PRE = "3"
8
+ MAJOR = 5
9
+ MINOR = 0
10
+ TINY = 0
11
+ PRE = "beta1"
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
14
  end
@@ -21,28 +21,27 @@ module ActiveModel
21
21
  # +self+.
22
22
  module Tests
23
23
 
24
- # == Responds to <tt>to_key</tt>
24
+ # Passes if the object's model responds to <tt>to_key</tt> and if calling
25
+ # this method returns +nil+ when the object is not persisted.
26
+ # Fails otherwise.
25
27
  #
26
- # Returns an Enumerable of all (primary) key attributes
27
- # or nil if <tt>model.persisted?</tt> is false. This is used by
28
- # <tt>dom_id</tt> to generate unique ids for the object.
28
+ # <tt>to_key</tt> returns an Enumerable of all (primary) key attributes
29
+ # of the model, and is used to a generate unique DOM id for the object.
29
30
  def test_to_key
30
31
  assert model.respond_to?(:to_key), "The model should respond to to_key"
31
32
  def model.persisted?() false end
32
33
  assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
33
34
  end
34
35
 
35
- # == Responds to <tt>to_param</tt>
36
- #
37
- # Returns a string representing the object's key suitable for use in URLs
38
- # or +nil+ if <tt>model.persisted?</tt> is +false+.
36
+ # Passes if the object's model responds to <tt>to_param</tt> and if
37
+ # calling this method returns +nil+ when the object is not persisted.
38
+ # Fails otherwise.
39
39
  #
40
+ # <tt>to_param</tt> is used to represent the object's key in URLs.
40
41
  # Implementers can decide to either raise an exception or provide a
41
42
  # default in case the record uses a composite primary key. There are no
42
43
  # tests for this behavior in lint because it doesn't make sense to force
43
44
  # any of the possible implementation strategies on the implementer.
44
- # However, if the resource is not persisted?, then <tt>to_param</tt>
45
- # should always return +nil+.
46
45
  def test_to_param
47
46
  assert model.respond_to?(:to_param), "The model should respond to to_param"
48
47
  def model.to_key() [1] end
@@ -50,32 +49,34 @@ module ActiveModel
50
49
  assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
51
50
  end
52
51
 
53
- # == Responds to <tt>to_partial_path</tt>
52
+ # Passes if the object's model responds to <tt>to_partial_path</tt> and if
53
+ # calling this method returns a string. Fails otherwise.
54
54
  #
55
- # Returns a string giving a relative path. This is used for looking up
56
- # partials. For example, a BlogPost model might return "blog_posts/blog_post"
55
+ # <tt>to_partial_path</tt> is used for looking up partials. For example,
56
+ # a BlogPost model might return "blog_posts/blog_post".
57
57
  def test_to_partial_path
58
58
  assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
59
59
  assert_kind_of String, model.to_partial_path
60
60
  end
61
61
 
62
- # == Responds to <tt>persisted?</tt>
62
+ # Passes if the object's model responds to <tt>persisted?</tt> and if
63
+ # calling this method returns either +true+ or +false+. Fails otherwise.
63
64
  #
64
- # Returns a boolean that specifies whether the object has been persisted
65
- # yet. This is used when calculating the URL for an object. If the object
66
- # is not persisted, a form for that object, for instance, will route to
67
- # the create action. If it is persisted, a form for the object will routes
68
- # to the update action.
65
+ # <tt>persisted?</tt> is used when calculating the URL for an object.
66
+ # If the object is not persisted, a form for that object, for instance,
67
+ # will route to the create action. If it is persisted, a form for the
68
+ # object will route to the update action.
69
69
  def test_persisted?
70
70
  assert model.respond_to?(:persisted?), "The model should respond to persisted?"
71
71
  assert_boolean model.persisted?, "persisted?"
72
72
  end
73
73
 
74
- # == \Naming
74
+ # Passes if the object's model responds to <tt>model_name</tt> both as
75
+ # an instance method and as a class method, and if calling this method
76
+ # returns a string with some convenience methods: <tt>:human</tt>,
77
+ # <tt>:singular</tt> and <tt>:plural</tt>.
75
78
  #
76
- # Model.model_name and Model#model_name must return a string with some
77
- # convenience methods: # <tt>:human</tt>, <tt>:singular</tt> and
78
- # <tt>:plural</tt>. Check ActiveModel::Naming for more information.
79
+ # Check ActiveModel::Naming for more information.
79
80
  def test_model_naming
80
81
  assert model.class.respond_to?(:model_name), "The model class should respond to model_name"
81
82
  model_name = model.class.model_name
@@ -88,12 +89,15 @@ module ActiveModel
88
89
  assert_equal model.model_name, model.class.model_name
89
90
  end
90
91
 
91
- # == \Errors Testing
92
+ # Passes if the object's model responds to <tt>errors</tt> and if calling
93
+ # <tt>[](attribute)</tt> on the result of this method returns an array.
94
+ # Fails otherwise.
92
95
  #
93
- # Returns an object that implements [](attribute) defined which returns an
94
- # Array of Strings that are the errors for the attribute in question.
95
- # If localization is used, the Strings should be localized for the current
96
- # locale. If no error is present, this method should return an empty Array.
96
+ # <tt>errors[attribute]</tt> is used to retrieve the errors of a model
97
+ # for a given attribute. If errors are present, the method should return
98
+ # an array of strings that are the errors for the attribute in question.
99
+ # If localization is used, the strings should be localized for the current
100
+ # locale. If no error is present, the method should return an empty array.
97
101
  def test_errors_aref
98
102
  assert model.respond_to?(:errors), "The model should respond to errors"
99
103
  assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
@@ -6,6 +6,7 @@ en:
6
6
  # The values :model, :attribute and :value are always available for interpolation
7
7
  # The value :count is available when applicable. Can be used for pluralization.
8
8
  messages:
9
+ model_invalid: "Validation failed: %{errors}"
9
10
  inclusion: "is not included in the list"
10
11
  exclusion: "is reserved"
11
12
  invalid: "is invalid"
@@ -16,7 +17,7 @@ en:
16
17
  present: "must be blank"
17
18
  too_long:
18
19
  one: "is too long (maximum is 1 character)"
19
- other: "is too long (maximum is %{count} characters)"
20
+ other: "is too long (maximum is %{count} characters)"
20
21
  too_short:
21
22
  one: "is too short (minimum is 1 character)"
22
23
  other: "is too short (minimum is %{count} characters)"
@@ -57,6 +57,7 @@ module ActiveModel
57
57
  # (see below).
58
58
  module Model
59
59
  extend ActiveSupport::Concern
60
+ include ActiveModel::AttributeAssignment
60
61
  include ActiveModel::Validations
61
62
  include ActiveModel::Conversion
62
63
 
@@ -75,10 +76,8 @@ module ActiveModel
75
76
  # person = Person.new(name: 'bob', age: '18')
76
77
  # person.name # => "bob"
77
78
  # person.age # => "18"
78
- def initialize(params={})
79
- params.each do |attr, value|
80
- self.public_send("#{attr}=", value)
81
- end if params
79
+ def initialize(attributes={})
80
+ assign_attributes(attributes) if attributes
82
81
 
83
82
  super()
84
83
  end
@@ -1,6 +1,7 @@
1
1
  require 'active_support/core_ext/hash/except'
2
2
  require 'active_support/core_ext/module/introspection'
3
3
  require 'active_support/core_ext/module/remove_method'
4
+ require 'active_support/core_ext/module/delegation'
4
5
 
5
6
  module ActiveModel
6
7
  class Name
@@ -163,7 +164,7 @@ module ActiveModel
163
164
  @route_key << "_index" if @plural == @singular
164
165
  end
165
166
 
166
- # Transform the model name into a more humane format, using I18n. By default,
167
+ # Transform the model name into a more human format, using I18n. By default,
167
168
  # it will underscore then humanize the class name.
168
169
  #
169
170
  # class BlogPost
@@ -190,8 +191,8 @@ module ActiveModel
190
191
 
191
192
  private
192
193
 
193
- def _singularize(string, replacement='_')
194
- ActiveSupport::Inflector.underscore(string).tr('/', replacement)
194
+ def _singularize(string)
195
+ ActiveSupport::Inflector.underscore(string).tr('/'.freeze, '_'.freeze)
195
196
  end
196
197
  end
197
198
 
@@ -212,7 +213,7 @@ module ActiveModel
212
213
  # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
213
214
  #
214
215
  # Providing the functionality that ActiveModel::Naming provides in your object
215
- # is required to pass the Active Model Lint test. So either extending the
216
+ # is required to pass the \Active \Model Lint test. So either extending the
216
217
  # provided method below, or rolling your own is required.
217
218
  module Naming
218
219
  def self.extended(base) #:nodoc:
@@ -26,7 +26,7 @@ module ActiveModel
26
26
  # it). When this attribute has a +nil+ value, the validation will not be
27
27
  # triggered.
28
28
  #
29
- # For further customizability, it is possible to supress the default
29
+ # For further customizability, it is possible to suppress the default
30
30
  # validations by passing <tt>validations: false</tt> as an argument.
31
31
  #
32
32
  # Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
@@ -77,13 +77,6 @@ module ActiveModel
77
77
  validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
78
78
  validates_confirmation_of :password, allow_blank: true
79
79
  end
80
-
81
- # This code is necessary as long as the protected_attributes gem is supported.
82
- if respond_to?(:attributes_protected_by_default)
83
- def self.attributes_protected_by_default #:nodoc:
84
- super + ['password_digest']
85
- end
86
- end
87
80
  end
88
81
  end
89
82
 
@@ -99,7 +92,7 @@ module ActiveModel
99
92
  # user.authenticate('notright') # => false
100
93
  # user.authenticate('mUc3m00RsqyRe') # => user
101
94
  def authenticate(unencrypted_password)
102
- BCrypt::Password.new(password_digest) == unencrypted_password && self
95
+ BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
103
96
  end
104
97
 
105
98
  attr_reader :password
@@ -31,16 +31,14 @@ module ActiveModel
31
31
  # of the attributes hash's keys. In order to override this behavior, take a look
32
32
  # at the private method +read_attribute_for_serialization+.
33
33
  #
34
- # Most of the time though, either the JSON or XML serializations are needed.
35
- # Both of these modules automatically include the
36
- # <tt>ActiveModel::Serialization</tt> module, so there is no need to
37
- # explicitly include it.
34
+ # ActiveModel::Serializers::JSON module automatically includes
35
+ # the <tt>ActiveModel::Serialization</tt> module, so there is no need to
36
+ # explicitly include <tt>ActiveModel::Serialization</tt>.
38
37
  #
39
- # A minimal implementation including XML and JSON would be:
38
+ # A minimal implementation including JSON would be:
40
39
  #
41
40
  # class Person
42
41
  # include ActiveModel::Serializers::JSON
43
- # include ActiveModel::Serializers::Xml
44
42
  #
45
43
  # attr_accessor :name
46
44
  #
@@ -55,13 +53,11 @@ module ActiveModel
55
53
  # person.serializable_hash # => {"name"=>nil}
56
54
  # person.as_json # => {"name"=>nil}
57
55
  # person.to_json # => "{\"name\":null}"
58
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
59
56
  #
60
57
  # person.name = "Bob"
61
58
  # person.serializable_hash # => {"name"=>"Bob"}
62
59
  # person.as_json # => {"name"=>"Bob"}
63
60
  # person.to_json # => "{\"name\":\"Bob\"}"
64
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
65
61
  #
66
62
  # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
67
63
  # <tt>:include</tt>. The following are all valid examples:
@@ -94,6 +90,37 @@ module ActiveModel
94
90
  # person.serializable_hash(except: :name) # => {"age"=>22}
95
91
  # person.serializable_hash(methods: :capitalized_name)
96
92
  # # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
93
+ #
94
+ # Example with <tt>:include</tt> option
95
+ #
96
+ # class User
97
+ # include ActiveModel::Serializers::JSON
98
+ # attr_accessor :name, :notes # Emulate has_many :notes
99
+ # def attributes
100
+ # {'name' => nil}
101
+ # end
102
+ # end
103
+ #
104
+ # class Note
105
+ # include ActiveModel::Serializers::JSON
106
+ # attr_accessor :title, :text
107
+ # def attributes
108
+ # {'title' => nil, 'text' => nil}
109
+ # end
110
+ # end
111
+ #
112
+ # note = Note.new
113
+ # note.title = 'Battle of Austerlitz'
114
+ # note.text = 'Some text here'
115
+ #
116
+ # user = User.new
117
+ # user.name = 'Napoleon'
118
+ # user.notes = [note]
119
+ #
120
+ # user.serializable_hash
121
+ # # => {"name" => "Napoleon"}
122
+ # user.serializable_hash(include: { notes: { only: 'title' }})
123
+ # # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
97
124
  def serializable_hash(options = nil)
98
125
  options ||= {}
99
126
 
@@ -107,7 +134,7 @@ module ActiveModel
107
134
  hash = {}
108
135
  attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
109
136
 
110
- Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
137
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
111
138
 
112
139
  serializable_add_includes(options) do |association, records, opts|
113
140
  hash[association.to_s] = if records.respond_to?(:to_ary)
@@ -10,7 +10,7 @@ module ActiveModel
10
10
  included do
11
11
  extend ActiveModel::Naming
12
12
 
13
- class_attribute :include_root_in_json, instance_writer: false
13
+ class_attribute :include_root_in_json
14
14
  self.include_root_in_json = false
15
15
  end
16
16
 
@@ -0,0 +1,59 @@
1
+ require 'active_model/type/helpers'
2
+ require 'active_model/type/value'
3
+
4
+ require 'active_model/type/big_integer'
5
+ require 'active_model/type/binary'
6
+ require 'active_model/type/boolean'
7
+ require 'active_model/type/date'
8
+ require 'active_model/type/date_time'
9
+ require 'active_model/type/decimal'
10
+ require 'active_model/type/decimal_without_scale'
11
+ require 'active_model/type/float'
12
+ require 'active_model/type/immutable_string'
13
+ require 'active_model/type/integer'
14
+ require 'active_model/type/string'
15
+ require 'active_model/type/text'
16
+ require 'active_model/type/time'
17
+ require 'active_model/type/unsigned_integer'
18
+
19
+ require 'active_model/type/registry'
20
+
21
+ module ActiveModel
22
+ module Type
23
+ @registry = Registry.new
24
+
25
+ class << self
26
+ attr_accessor :registry # :nodoc:
27
+ delegate :add_modifier, to: :registry
28
+
29
+ # Add a new type to the registry, allowing it to be referenced as a
30
+ # symbol by ActiveModel::Attributes::ClassMethods#attribute. If your
31
+ # type is only meant to be used with a specific database adapter, you can
32
+ # do so by passing +adapter: :postgresql+. If your type has the same
33
+ # name as a native type for the current adapter, an exception will be
34
+ # raised unless you specify an +:override+ option. +override: true+ will
35
+ # cause your type to be used instead of the native type. +override:
36
+ # false+ will cause the native type to be used over yours if one exists.
37
+ def register(type_name, klass = nil, **options, &block)
38
+ registry.register(type_name, klass, **options, &block)
39
+ end
40
+
41
+ def lookup(*args, **kwargs) # :nodoc:
42
+ registry.lookup(*args, **kwargs)
43
+ end
44
+ end
45
+
46
+ register(:big_integer, Type::BigInteger)
47
+ register(:binary, Type::Binary)
48
+ register(:boolean, Type::Boolean)
49
+ register(:date, Type::Date)
50
+ register(:date_time, Type::DateTime)
51
+ register(:decimal, Type::Decimal)
52
+ register(:float, Type::Float)
53
+ register(:immutable_string, Type::ImmutableString)
54
+ register(:integer, Type::Integer)
55
+ register(:string, Type::String)
56
+ register(:text, Type::Text)
57
+ register(:time, Type::Time)
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_model/type/integer'
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class BigInteger < Integer # :nodoc:
6
+ private
7
+
8
+ def max_value
9
+ ::Float::INFINITY
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ module ActiveModel
2
+ module Type
3
+ class Binary < Value # :nodoc:
4
+ def type
5
+ :binary
6
+ end
7
+
8
+ def binary?
9
+ true
10
+ end
11
+
12
+ def cast(value)
13
+ if value.is_a?(Data)
14
+ value.to_s
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def serialize(value)
21
+ return if value.nil?
22
+ Data.new(super)
23
+ end
24
+
25
+ def changed_in_place?(raw_old_value, value)
26
+ old_value = deserialize(raw_old_value)
27
+ old_value != value
28
+ end
29
+
30
+ class Data # :nodoc:
31
+ def initialize(value)
32
+ @value = value.to_s
33
+ end
34
+
35
+ def to_s
36
+ @value
37
+ end
38
+ alias_method :to_str, :to_s
39
+
40
+ def hex
41
+ @value.unpack('H*')[0]
42
+ end
43
+
44
+ def ==(other)
45
+ other == to_s || super
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end