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
@@ -3,14 +3,16 @@ module ActiveModel
3
3
  module Validations
4
4
  class ConfirmationValidator < EachValidator # :nodoc:
5
5
  def initialize(options)
6
- super
6
+ super({ case_sensitive: true }.merge!(options))
7
7
  setup!(options[:class])
8
8
  end
9
9
 
10
10
  def validate_each(record, attribute, value)
11
- if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
12
- human_attribute_name = record.class.human_attribute_name(attribute)
13
- record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(attribute: human_attribute_name))
11
+ if (confirmed = record.send("#{attribute}_confirmation"))
12
+ unless confirmation_value_equal?(record, attribute, value, confirmed)
13
+ human_attribute_name = record.class.human_attribute_name(attribute)
14
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, options.except(:case_sensitive).merge!(attribute: human_attribute_name))
15
+ end
14
16
  end
15
17
  end
16
18
 
@@ -24,6 +26,14 @@ module ActiveModel
24
26
  :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
25
27
  end.compact)
26
28
  end
29
+
30
+ def confirmation_value_equal?(record, attribute, value, confirmed)
31
+ if !options[:case_sensitive] && value.is_a?(String)
32
+ value.casecmp(confirmed) == 0
33
+ else
34
+ value == confirmed
35
+ end
36
+ end
27
37
  end
28
38
 
29
39
  module HelperMethods
@@ -55,6 +65,8 @@ module ActiveModel
55
65
  # Configuration options:
56
66
  # * <tt>:message</tt> - A custom error message (default is: "doesn't match
57
67
  # <tt>%{translated_attribute_name}</tt>").
68
+ # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
69
+ # non-text columns (+true+ by default).
58
70
  #
59
71
  # There is also a list of default options supported by every validator:
60
72
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
@@ -29,7 +29,9 @@ module ActiveModel
29
29
  # Configuration options:
30
30
  # * <tt>:in</tt> - An enumerable object of items that the value shouldn't
31
31
  # be part of. This can be supplied as a proc, lambda or symbol which returns an
32
- # enumerable. If the enumerable is a range the test is performed with
32
+ # enumerable. If the enumerable is a numerical, time or datetime range the test
33
+ # is performed with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When
34
+ # using a proc or lambda the instance under validation is passed as an argument.
33
35
  # * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
34
36
  # <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
35
37
  # * <tt>:message</tt> - Specifies a custom error message (default is: "is
@@ -77,7 +77,7 @@ module ActiveModel
77
77
  # with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
78
78
  # end
79
79
  #
80
- # Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
80
+ # Note: use <tt>\A</tt> and <tt>\z</tt> to match the start and end of the
81
81
  # string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
82
82
  #
83
83
  # Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
@@ -0,0 +1,13 @@
1
+ module ActiveModel
2
+ module Validations
3
+ module HelperMethods # :nodoc:
4
+ private
5
+ def _merge_attributes(attr_names)
6
+ options = attr_names.extract_options!.symbolize_keys
7
+ attr_names.flatten!
8
+ options[:attributes] = attr_names
9
+ options
10
+ end
11
+ end
12
+ end
13
+ end
@@ -28,9 +28,9 @@ module ActiveModel
28
28
  # Configuration options:
29
29
  # * <tt>:in</tt> - An enumerable object of available items. This can be
30
30
  # supplied as a proc, lambda or symbol which returns an enumerable. If the
31
- # enumerable is a numerical range the test is performed with <tt>Range#cover?</tt>,
32
- # otherwise with <tt>include?</tt>. When using a proc or lambda the instance
33
- # under validation is passed as an argument.
31
+ # enumerable is a numerical, time or datetime range the test is performed
32
+ # with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When using
33
+ # a proc or lambda the instance under validation is passed as an argument.
34
34
  # * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
35
35
  # * <tt>:message</tt> - Specifies a custom error message (default is: "is
36
36
  # not included in the list").
@@ -1,6 +1,6 @@
1
- module ActiveModel
1
+ require "active_support/core_ext/string/strip"
2
2
 
3
- # == Active \Model Length Validator
3
+ module ActiveModel
4
4
  module Validations
5
5
  class LengthValidator < EachValidator # :nodoc:
6
6
  MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
@@ -18,6 +18,27 @@ module ActiveModel
18
18
  options[:minimum] = 1
19
19
  end
20
20
 
21
+ if options[:tokenizer]
22
+ ActiveSupport::Deprecation.warn(<<-EOS.strip_heredoc)
23
+ The `:tokenizer` option is deprecated, and will be removed in Rails 5.1.
24
+ You can achieve the same functionality by defining an instance method
25
+ with the value that you want to validate the length of. For example,
26
+
27
+ validates_length_of :essay, minimum: 100,
28
+ tokenizer: ->(str) { str.scan(/\w+/) }
29
+
30
+ should be written as
31
+
32
+ validates_length_of :words_in_essay, minimum: 100
33
+
34
+ private
35
+
36
+ def words_in_essay
37
+ essay.scan(/\w+/)
38
+ end
39
+ EOS
40
+ end
41
+
21
42
  super
22
43
  end
23
44
 
@@ -38,7 +59,7 @@ module ActiveModel
38
59
  end
39
60
 
40
61
  def validate_each(record, attribute, value)
41
- value = tokenize(value)
62
+ value = tokenize(record, value)
42
63
  value_length = value.respond_to?(:length) ? value.length : value.to_s.length
43
64
  errors_options = options.except(*RESERVED_OPTIONS)
44
65
 
@@ -59,10 +80,14 @@ module ActiveModel
59
80
  end
60
81
 
61
82
  private
62
-
63
- def tokenize(value)
64
- if options[:tokenizer] && value.kind_of?(String)
65
- options[:tokenizer].call(value)
83
+ def tokenize(record, value)
84
+ tokenizer = options[:tokenizer]
85
+ if tokenizer && value.kind_of?(String)
86
+ if tokenizer.kind_of?(Proc)
87
+ tokenizer.call(value)
88
+ elsif record.respond_to?(tokenizer)
89
+ record.send(tokenizer, value)
90
+ end
66
91
  end || value
67
92
  end
68
93
 
@@ -73,8 +98,9 @@ module ActiveModel
73
98
 
74
99
  module HelperMethods
75
100
 
76
- # Validates that the specified attribute matches the length restrictions
77
- # supplied. Only one option can be used at a time:
101
+ # Validates that the specified attributes match the length restrictions
102
+ # supplied. Only one constraint option can be used at a time apart from
103
+ # +:minimum+ and +:maximum+ that can be combined together:
78
104
  #
79
105
  # class Person < ActiveRecord::Base
80
106
  # validates_length_of :first_name, maximum: 30
@@ -84,18 +110,27 @@ module ActiveModel
84
110
  # validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
85
111
  # validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
86
112
  # validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
87
- # validates_length_of :essay, minimum: 100, too_short: 'Your essay must be at least 100 words.',
88
- # tokenizer: ->(str) { str.scan(/\w+/) }
113
+ # validates_length_of :words_in_essay, minimum: 100, too_short: 'Your essay must be at least 100 words.'
114
+ #
115
+ # private
116
+ #
117
+ # def words_in_essay
118
+ # essay.scan(/\w+/)
119
+ # end
89
120
  # end
90
121
  #
91
- # Configuration options:
122
+ # Constraint options:
123
+ #
92
124
  # * <tt>:minimum</tt> - The minimum size of the attribute.
93
125
  # * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
94
- # default if not used with :minimum.
126
+ # default if not used with +:minimum+.
95
127
  # * <tt>:is</tt> - The exact size of the attribute.
96
128
  # * <tt>:within</tt> - A range specifying the minimum and maximum size of
97
129
  # the attribute.
98
130
  # * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
131
+ #
132
+ # Other options:
133
+ #
99
134
  # * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
100
135
  # * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
101
136
  # * <tt>:too_long</tt> - The error message if the attribute goes over the
@@ -108,10 +143,6 @@ module ActiveModel
108
143
  # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
109
144
  # <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
110
145
  # <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
111
- # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
112
- # (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words
113
- # as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
114
- # which counts individual characters.
115
146
  #
116
147
  # There is also a list of default options supported by every validator:
117
148
  # +:if+, +:unless+, +:on+ and +:strict+.
@@ -20,7 +20,7 @@ module ActiveModel
20
20
  def validate_each(record, attr_name, value)
21
21
  before_type_cast = :"#{attr_name}_before_type_cast"
22
22
 
23
- raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast)
23
+ raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast) && record.send(before_type_cast) != value
24
24
  raw_value ||= value
25
25
 
26
26
  if record_attribute_changed_in_place?(record, attr_name)
@@ -29,16 +29,14 @@ module ActiveModel
29
29
 
30
30
  return if options[:allow_nil] && raw_value.nil?
31
31
 
32
- unless value = parse_raw_value_as_a_number(raw_value)
32
+ unless is_number?(raw_value)
33
33
  record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
34
34
  return
35
35
  end
36
36
 
37
- if allow_only_integer?(record)
38
- unless value = parse_raw_value_as_an_integer(raw_value)
39
- record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
40
- return
41
- end
37
+ if allow_only_integer?(record) && !is_integer?(raw_value)
38
+ record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
39
+ return
42
40
  end
43
41
 
44
42
  options.slice(*CHECKS.keys).each do |option, option_value|
@@ -64,14 +62,15 @@ module ActiveModel
64
62
 
65
63
  protected
66
64
 
67
- def parse_raw_value_as_a_number(raw_value)
68
- Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
65
+ def is_number?(raw_value)
66
+ parsed_value = Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
67
+ !parsed_value.nil?
69
68
  rescue ArgumentError, TypeError
70
- nil
69
+ false
71
70
  end
72
71
 
73
- def parse_raw_value_as_an_integer(raw_value)
74
- raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\z/
72
+ def is_integer?(raw_value)
73
+ /\A[+-]?\d+\z/ === raw_value.to_s
75
74
  end
76
75
 
77
76
  def filtered_options(value)
@@ -114,7 +113,7 @@ module ActiveModel
114
113
  # * <tt>:only_integer</tt> - Specifies whether the value has to be an
115
114
  # integer, e.g. an integral value (default is +false+).
116
115
  # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
117
- # +false+). Notice that for Integer and Float columns empty strings are
116
+ # +false+). Notice that for fixnum and float columns empty strings are
118
117
  # converted to +nil+.
119
118
  # * <tt>:greater_than</tt> - Specifies the value must be greater than the
120
119
  # supplied value.
@@ -115,7 +115,7 @@ module ActiveModel
115
115
  key = "#{key.to_s.camelize}Validator"
116
116
 
117
117
  begin
118
- validator = key.include?('::') ? key.constantize : const_get(key)
118
+ validator = key.include?('::'.freeze) ? key.constantize : const_get(key)
119
119
  rescue NameError
120
120
  raise ArgumentError, "Unknown validator: '#{key}'"
121
121
  end
@@ -1,15 +1,5 @@
1
1
  module ActiveModel
2
2
  module Validations
3
- module HelperMethods
4
- private
5
- def _merge_attributes(attr_names)
6
- options = attr_names.extract_options!.symbolize_keys
7
- attr_names.flatten!
8
- options[:attributes] = attr_names
9
- options
10
- end
11
- end
12
-
13
3
  class WithValidator < EachValidator # :nodoc:
14
4
  def validate_each(record, attr, val)
15
5
  method_name = options[:with]
@@ -15,7 +15,7 @@ module ActiveModel
15
15
  # class MyValidator < ActiveModel::Validator
16
16
  # def validate(record)
17
17
  # if some_complex_logic
18
- # record.errors[:base] = "This record is invalid"
18
+ # record.errors.add(:base, "This record is invalid")
19
19
  # end
20
20
  # end
21
21
  #
@@ -127,7 +127,7 @@ module ActiveModel
127
127
  # in the options hash invoking the <tt>validate_each</tt> method passing in the
128
128
  # record, attribute and value.
129
129
  #
130
- # All Active Model validations are built on top of this validator.
130
+ # All \Active \Model validations are built on top of this validator.
131
131
  class EachValidator < Validator #:nodoc:
132
132
  attr_reader :attributes
133
133
 
@@ -163,6 +163,10 @@ module ActiveModel
163
163
  # +ArgumentError+ when invalid options are supplied.
164
164
  def check_validity!
165
165
  end
166
+
167
+ def should_validate?(record) # :nodoc:
168
+ !record.persisted? || record.changed? || record.marked_for_destruction?
169
+ end
166
170
  end
167
171
 
168
172
  # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
@@ -1,7 +1,7 @@
1
1
  require_relative 'gem_version'
2
2
 
3
3
  module ActiveModel
4
- # Returns the version of the currently loaded ActiveModel as a <tt>Gem::Version</tt>
4
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
5
5
  def self.version
6
6
  gem_version
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemodel
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.11.3
4
+ version: 5.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-15 00:00:00.000000000 Z
11
+ date: 2015-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 4.2.11.3
19
+ version: 5.0.0.beta1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 4.2.11.3
26
+ version: 5.0.0.beta1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: builder
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -50,6 +50,7 @@ files:
50
50
  - MIT-LICENSE
51
51
  - README.rdoc
52
52
  - lib/active_model.rb
53
+ - lib/active_model/attribute_assignment.rb
53
54
  - lib/active_model/attribute_methods.rb
54
55
  - lib/active_model/callbacks.rb
55
56
  - lib/active_model/conversion.rb
@@ -65,9 +66,30 @@ files:
65
66
  - lib/active_model/secure_password.rb
66
67
  - lib/active_model/serialization.rb
67
68
  - lib/active_model/serializers/json.rb
68
- - lib/active_model/serializers/xml.rb
69
69
  - lib/active_model/test_case.rb
70
70
  - lib/active_model/translation.rb
71
+ - lib/active_model/type.rb
72
+ - lib/active_model/type/big_integer.rb
73
+ - lib/active_model/type/binary.rb
74
+ - lib/active_model/type/boolean.rb
75
+ - lib/active_model/type/date.rb
76
+ - lib/active_model/type/date_time.rb
77
+ - lib/active_model/type/decimal.rb
78
+ - lib/active_model/type/decimal_without_scale.rb
79
+ - lib/active_model/type/float.rb
80
+ - lib/active_model/type/helpers.rb
81
+ - lib/active_model/type/helpers/accepts_multiparameter_time.rb
82
+ - lib/active_model/type/helpers/mutable.rb
83
+ - lib/active_model/type/helpers/numeric.rb
84
+ - lib/active_model/type/helpers/time_value.rb
85
+ - lib/active_model/type/immutable_string.rb
86
+ - lib/active_model/type/integer.rb
87
+ - lib/active_model/type/registry.rb
88
+ - lib/active_model/type/string.rb
89
+ - lib/active_model/type/text.rb
90
+ - lib/active_model/type/time.rb
91
+ - lib/active_model/type/unsigned_integer.rb
92
+ - lib/active_model/type/value.rb
71
93
  - lib/active_model/validations.rb
72
94
  - lib/active_model/validations/absence.rb
73
95
  - lib/active_model/validations/acceptance.rb
@@ -76,6 +98,7 @@ files:
76
98
  - lib/active_model/validations/confirmation.rb
77
99
  - lib/active_model/validations/exclusion.rb
78
100
  - lib/active_model/validations/format.rb
101
+ - lib/active_model/validations/helper_methods.rb
79
102
  - lib/active_model/validations/inclusion.rb
80
103
  - lib/active_model/validations/length.rb
81
104
  - lib/active_model/validations/numericality.rb
@@ -96,15 +119,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
119
  requirements:
97
120
  - - ">="
98
121
  - !ruby/object:Gem::Version
99
- version: 1.9.3
122
+ version: 2.2.2
100
123
  required_rubygems_version: !ruby/object:Gem::Requirement
101
124
  requirements:
102
- - - ">="
125
+ - - ">"
103
126
  - !ruby/object:Gem::Version
104
- version: '0'
127
+ version: 1.3.1
105
128
  requirements: []
106
- rubygems_version: 3.0.3
129
+ rubyforge_project:
130
+ rubygems_version: 2.5.1
107
131
  signing_key:
108
132
  specification_version: 4
109
133
  summary: A toolkit for building modeling frameworks (part of Rails).
110
134
  test_files: []
135
+ has_rdoc:
@@ -1,238 +0,0 @@
1
- require 'active_support/core_ext/module/attribute_accessors'
2
- require 'active_support/core_ext/array/conversions'
3
- require 'active_support/core_ext/hash/conversions'
4
- require 'active_support/core_ext/hash/slice'
5
- require 'active_support/core_ext/time/acts_like'
6
-
7
- module ActiveModel
8
- module Serializers
9
- # == Active Model XML Serializer
10
- module Xml
11
- extend ActiveSupport::Concern
12
- include ActiveModel::Serialization
13
-
14
- included do
15
- extend ActiveModel::Naming
16
- end
17
-
18
- class Serializer #:nodoc:
19
- class Attribute #:nodoc:
20
- attr_reader :name, :value, :type
21
-
22
- def initialize(name, serializable, value)
23
- @name, @serializable = name, serializable
24
-
25
- if value.acts_like?(:time) && value.respond_to?(:in_time_zone)
26
- value = value.in_time_zone
27
- end
28
-
29
- @value = value
30
- @type = compute_type
31
- end
32
-
33
- def decorations
34
- decorations = {}
35
- decorations[:encoding] = 'base64' if type == :binary
36
- decorations[:type] = (type == :string) ? nil : type
37
- decorations[:nil] = true if value.nil?
38
- decorations
39
- end
40
-
41
- protected
42
-
43
- def compute_type
44
- return if value.nil?
45
- type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name]
46
- type ||= :string if value.respond_to?(:to_str)
47
- type ||= :yaml
48
- type
49
- end
50
- end
51
-
52
- class MethodAttribute < Attribute #:nodoc:
53
- end
54
-
55
- attr_reader :options
56
-
57
- def initialize(serializable, options = nil)
58
- @serializable = serializable
59
- @options = options ? options.dup : {}
60
- end
61
-
62
- def serializable_hash
63
- @serializable.serializable_hash(@options.except(:include))
64
- end
65
-
66
- def serializable_collection
67
- methods = Array(options[:methods]).map(&:to_s)
68
- serializable_hash.map do |name, value|
69
- name = name.to_s
70
- if methods.include?(name)
71
- self.class::MethodAttribute.new(name, @serializable, value)
72
- else
73
- self.class::Attribute.new(name, @serializable, value)
74
- end
75
- end
76
- end
77
-
78
- def serialize
79
- require 'builder' unless defined? ::Builder
80
-
81
- options[:indent] ||= 2
82
- options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
83
-
84
- @builder = options[:builder]
85
- @builder.instruct! unless options[:skip_instruct]
86
-
87
- root = (options[:root] || @serializable.model_name.element).to_s
88
- root = ActiveSupport::XmlMini.rename_key(root, options)
89
-
90
- args = [root]
91
- args << { xmlns: options[:namespace] } if options[:namespace]
92
- args << { type: options[:type] } if options[:type] && !options[:skip_types]
93
-
94
- @builder.tag!(*args) do
95
- add_attributes_and_methods
96
- add_includes
97
- add_extra_behavior
98
- add_procs
99
- yield @builder if block_given?
100
- end
101
- end
102
-
103
- private
104
-
105
- def add_extra_behavior
106
- end
107
-
108
- def add_attributes_and_methods
109
- serializable_collection.each do |attribute|
110
- key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
111
- ActiveSupport::XmlMini.to_tag(key, attribute.value,
112
- options.merge(attribute.decorations))
113
- end
114
- end
115
-
116
- def add_includes
117
- @serializable.send(:serializable_add_includes, options) do |association, records, opts|
118
- add_associations(association, records, opts)
119
- end
120
- end
121
-
122
- # TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
123
- def add_associations(association, records, opts)
124
- merged_options = opts.merge(options.slice(:builder, :indent))
125
- merged_options[:skip_instruct] = true
126
-
127
- [:skip_types, :dasherize, :camelize].each do |key|
128
- merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
129
- end
130
-
131
- if records.respond_to?(:to_ary)
132
- records = records.to_ary
133
-
134
- tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
135
- type = options[:skip_types] ? { } : { type: "array" }
136
- association_name = association.to_s.singularize
137
- merged_options[:root] = association_name
138
-
139
- if records.empty?
140
- @builder.tag!(tag, type)
141
- else
142
- @builder.tag!(tag, type) do
143
- records.each do |record|
144
- if options[:skip_types]
145
- record_type = {}
146
- else
147
- record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
148
- record_type = { type: record_class }
149
- end
150
-
151
- record.to_xml merged_options.merge(record_type)
152
- end
153
- end
154
- end
155
- else
156
- merged_options[:root] = association.to_s
157
-
158
- unless records.class.to_s.underscore == association.to_s
159
- merged_options[:type] = records.class.name
160
- end
161
-
162
- records.to_xml merged_options
163
- end
164
- end
165
-
166
- def add_procs
167
- if procs = options.delete(:procs)
168
- Array(procs).each do |proc|
169
- if proc.arity == 1
170
- proc.call(options)
171
- else
172
- proc.call(options, @serializable)
173
- end
174
- end
175
- end
176
- end
177
- end
178
-
179
- # Returns XML representing the model. Configuration can be
180
- # passed through +options+.
181
- #
182
- # Without any +options+, the returned XML string will include all the
183
- # model's attributes.
184
- #
185
- # user = User.find(1)
186
- # user.to_xml
187
- #
188
- # <?xml version="1.0" encoding="UTF-8"?>
189
- # <user>
190
- # <id type="integer">1</id>
191
- # <name>David</name>
192
- # <age type="integer">16</age>
193
- # <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
194
- # </user>
195
- #
196
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
197
- # attributes included, and work similar to the +attributes+ method.
198
- #
199
- # To include the result of some method calls on the model use <tt>:methods</tt>.
200
- #
201
- # To include associations use <tt>:include</tt>.
202
- #
203
- # For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
204
- def to_xml(options = {}, &block)
205
- Serializer.new(self, options).serialize(&block)
206
- end
207
-
208
- # Sets the model +attributes+ from an XML string. Returns +self+.
209
- #
210
- # class Person
211
- # include ActiveModel::Serializers::Xml
212
- #
213
- # attr_accessor :name, :age, :awesome
214
- #
215
- # def attributes=(hash)
216
- # hash.each do |key, value|
217
- # instance_variable_set("@#{key}", value)
218
- # end
219
- # end
220
- #
221
- # def attributes
222
- # instance_values
223
- # end
224
- # end
225
- #
226
- # xml = { name: 'bob', age: 22, awesome:true }.to_xml
227
- # person = Person.new
228
- # person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
229
- # person.name # => "bob"
230
- # person.age # => 22
231
- # person.awesome # => true
232
- def from_xml(xml)
233
- self.attributes = Hash.from_xml(xml).values.first
234
- self
235
- end
236
- end
237
- end
238
- end