active_model_serializers 0.10.0.rc3 → 0.10.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +37 -33
  4. data/.rubocop_todo.yml +13 -88
  5. data/.simplecov +23 -11
  6. data/.travis.yml +17 -12
  7. data/CHANGELOG.md +417 -12
  8. data/CONTRIBUTING.md +206 -17
  9. data/Gemfile +12 -11
  10. data/README.md +78 -286
  11. data/Rakefile +44 -8
  12. data/active_model_serializers.gemspec +9 -1
  13. data/appveyor.yml +6 -4
  14. data/docs/ARCHITECTURE.md +120 -0
  15. data/docs/DESIGN.textile +8 -0
  16. data/docs/README.md +21 -15
  17. data/docs/general/adapters.md +79 -27
  18. data/docs/general/caching.md +52 -0
  19. data/docs/general/configuration_options.md +18 -2
  20. data/docs/general/getting_started.md +44 -19
  21. data/docs/general/instrumentation.md +40 -0
  22. data/docs/general/logging.md +14 -0
  23. data/docs/general/rendering.md +153 -0
  24. data/docs/general/serializers.md +207 -0
  25. data/docs/how-open-source-maintained.jpg +0 -0
  26. data/docs/howto/add_pagination_links.md +16 -7
  27. data/docs/howto/add_root_key.md +3 -3
  28. data/docs/howto/outside_controller_use.md +25 -9
  29. data/docs/howto/test.md +152 -0
  30. data/docs/integrations/ember-and-json-api.md +112 -0
  31. data/docs/integrations/grape.md +19 -0
  32. data/docs/jsonapi/schema.md +140 -0
  33. data/docs/jsonapi/schema/schema.json +366 -0
  34. data/lib/action_controller/serialization.rb +13 -9
  35. data/lib/active_model/serializable_resource.rb +9 -7
  36. data/lib/active_model/serializer.rb +93 -129
  37. data/lib/active_model/serializer/adapter.rb +37 -105
  38. data/lib/active_model/serializer/adapter/attributes.rb +66 -0
  39. data/lib/active_model/serializer/adapter/base.rb +58 -0
  40. data/lib/active_model/serializer/adapter/cached_serializer.rb +45 -0
  41. data/lib/active_model/serializer/adapter/fragment_cache.rb +43 -7
  42. data/lib/active_model/serializer/adapter/json.rb +11 -37
  43. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +9 -1
  44. data/lib/active_model/serializer/adapter/json_api.rb +127 -62
  45. data/lib/active_model/serializer/adapter/json_api/deserialization.rb +207 -0
  46. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +9 -1
  47. data/lib/active_model/serializer/adapter/json_api/link.rb +44 -0
  48. data/lib/active_model/serializer/adapter/json_api/pagination_links.rb +12 -4
  49. data/lib/active_model/serializer/adapter/null.rb +7 -1
  50. data/lib/active_model/serializer/array_serializer.rb +6 -37
  51. data/lib/active_model/serializer/associations.rb +21 -18
  52. data/lib/active_model/serializer/attribute.rb +25 -0
  53. data/lib/active_model/serializer/attributes.rb +82 -0
  54. data/lib/active_model/serializer/caching.rb +100 -0
  55. data/lib/active_model/serializer/collection_serializer.rb +47 -0
  56. data/lib/active_model/serializer/configuration.rb +17 -3
  57. data/lib/active_model/serializer/field.rb +56 -0
  58. data/lib/active_model/serializer/fieldset.rb +9 -18
  59. data/lib/active_model/serializer/include_tree.rb +111 -0
  60. data/lib/active_model/serializer/links.rb +33 -0
  61. data/lib/active_model/serializer/lint.rb +16 -3
  62. data/lib/active_model/serializer/reflection.rb +25 -8
  63. data/lib/active_model/serializer/type.rb +25 -0
  64. data/lib/active_model/serializer/version.rb +1 -1
  65. data/lib/active_model_serializers.rb +16 -26
  66. data/lib/active_model_serializers/callbacks.rb +55 -0
  67. data/lib/active_model_serializers/deserialization.rb +13 -0
  68. data/lib/active_model_serializers/logging.rb +119 -0
  69. data/lib/active_model_serializers/model.rb +39 -0
  70. data/lib/active_model_serializers/railtie.rb +38 -0
  71. data/lib/active_model_serializers/serialization_context.rb +10 -0
  72. data/lib/active_model_serializers/test.rb +7 -0
  73. data/lib/active_model_serializers/test/schema.rb +103 -0
  74. data/lib/active_model_serializers/test/serializer.rb +125 -0
  75. data/lib/generators/{serializer → rails}/USAGE +1 -1
  76. data/lib/generators/{serializer → rails}/resource_override.rb +1 -3
  77. data/lib/generators/{serializer → rails}/serializer_generator.rb +2 -3
  78. data/lib/generators/{serializer → rails}/templates/serializer.rb.erb +0 -0
  79. data/lib/grape/active_model_serializers.rb +14 -0
  80. data/lib/grape/formatters/active_model_serializers.rb +15 -0
  81. data/lib/grape/helpers/active_model_serializers.rb +16 -0
  82. data/test/action_controller/adapter_selector_test.rb +1 -1
  83. data/test/action_controller/json/include_test.rb +167 -0
  84. data/test/action_controller/json_api/deserialization_test.rb +59 -0
  85. data/test/action_controller/json_api/linked_test.rb +20 -3
  86. data/test/action_controller/json_api/pagination_test.rb +7 -7
  87. data/test/action_controller/serialization_scope_name_test.rb +8 -12
  88. data/test/action_controller/serialization_test.rb +44 -29
  89. data/test/active_model_serializers/logging_test.rb +77 -0
  90. data/test/active_model_serializers/model_test.rb +9 -0
  91. data/test/active_model_serializers/railtie_test_isolated.rb +57 -0
  92. data/test/active_model_serializers/serialization_context_test.rb +18 -0
  93. data/test/active_model_serializers/test/schema_test.rb +128 -0
  94. data/test/active_model_serializers/test/serializer_test.rb +63 -0
  95. data/test/active_record_test.rb +1 -1
  96. data/test/adapter/fragment_cache_test.rb +3 -2
  97. data/test/adapter/json/belongs_to_test.rb +2 -2
  98. data/test/adapter/json/collection_test.rb +15 -5
  99. data/test/adapter/json/has_many_test.rb +3 -3
  100. data/test/adapter/json_api/belongs_to_test.rb +3 -3
  101. data/test/adapter/json_api/collection_test.rb +8 -6
  102. data/test/adapter/json_api/fields_test.rb +89 -0
  103. data/test/adapter/json_api/has_many_embed_ids_test.rb +2 -2
  104. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +2 -2
  105. data/test/adapter/json_api/has_many_test.rb +3 -3
  106. data/test/adapter/json_api/has_one_test.rb +2 -2
  107. data/test/adapter/json_api/json_api_test.rb +2 -2
  108. data/test/adapter/json_api/linked_test.rb +116 -5
  109. data/test/adapter/json_api/links_test.rb +68 -0
  110. data/test/adapter/json_api/pagination_links_test.rb +4 -4
  111. data/test/adapter/json_api/parse_test.rb +139 -0
  112. data/test/adapter/json_api/resource_type_config_test.rb +27 -15
  113. data/test/adapter/json_api/toplevel_jsonapi_test.rb +84 -0
  114. data/test/adapter/json_test.rb +2 -2
  115. data/test/adapter/null_test.rb +2 -2
  116. data/test/adapter_test.rb +3 -3
  117. data/test/array_serializer_test.rb +30 -93
  118. data/test/collection_serializer_test.rb +100 -0
  119. data/test/fixtures/poro.rb +19 -51
  120. data/test/generators/scaffold_controller_generator_test.rb +1 -0
  121. data/test/generators/serializer_generator_test.rb +2 -1
  122. data/test/grape_test.rb +82 -0
  123. data/test/include_tree/from_include_args_test.rb +26 -0
  124. data/test/include_tree/from_string_test.rb +94 -0
  125. data/test/include_tree/include_args_to_hash_test.rb +64 -0
  126. data/test/lint_test.rb +4 -1
  127. data/test/logger_test.rb +2 -2
  128. data/test/poro_test.rb +1 -1
  129. data/test/serializable_resource_test.rb +1 -1
  130. data/test/serializers/adapter_for_test.rb +24 -28
  131. data/test/serializers/association_macros_test.rb +1 -1
  132. data/test/serializers/associations_test.rb +143 -26
  133. data/test/serializers/attribute_test.rb +64 -3
  134. data/test/serializers/attributes_test.rb +1 -6
  135. data/test/serializers/cache_test.rb +46 -2
  136. data/test/serializers/configuration_test.rb +20 -3
  137. data/test/serializers/fieldset_test.rb +5 -16
  138. data/test/serializers/meta_test.rb +38 -29
  139. data/test/serializers/options_test.rb +1 -1
  140. data/test/serializers/root_test.rb +1 -1
  141. data/test/serializers/serializer_for_test.rb +78 -9
  142. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  143. data/test/support/isolated_unit.rb +77 -0
  144. data/test/support/rails5_shims.rb +29 -0
  145. data/test/support/rails_app.rb +7 -3
  146. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  147. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  148. data/test/support/schemas/custom/show.json +7 -0
  149. data/test/support/schemas/hyper_schema.json +93 -0
  150. data/test/support/schemas/render_using_json_api.json +43 -0
  151. data/test/support/schemas/simple_json_pointers.json +10 -0
  152. data/test/support/serialization_testing.rb +46 -6
  153. data/test/support/test_case.rb +14 -0
  154. data/test/test_helper.rb +21 -14
  155. metadata +160 -16
  156. data/lib/active_model/serializer/adapter/flatten_json.rb +0 -12
  157. data/lib/active_model/serializer/railtie.rb +0 -15
  158. data/lib/active_model/serializer/utils.rb +0 -35
  159. data/lib/tasks/rubocop.rake +0 -0
  160. data/test/capture_warnings.rb +0 -65
  161. data/test/utils/include_args_to_hash_test.rb +0 -79
@@ -0,0 +1,207 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ module Adapter
4
+ class JsonApi
5
+ # NOTE(Experimental):
6
+ # This is an experimental feature. Both the interface and internals could be subject
7
+ # to changes.
8
+ module Deserialization
9
+ InvalidDocument = Class.new(ArgumentError)
10
+
11
+ module_function
12
+
13
+ # Transform a JSON API document, containing a single data object,
14
+ # into a hash that is ready for ActiveRecord::Base.new() and such.
15
+ # Raises InvalidDocument if the payload is not properly formatted.
16
+ #
17
+ # @param [Hash|ActionController::Parameters] document
18
+ # @param [Hash] options
19
+ # only: Array of symbols of whitelisted fields.
20
+ # except: Array of symbols of blacklisted fields.
21
+ # keys: Hash of translated keys (e.g. :author => :user).
22
+ # polymorphic: Array of symbols of polymorphic fields.
23
+ # @return [Hash]
24
+ #
25
+ # @example
26
+ # document = {
27
+ # data: {
28
+ # id: 1,
29
+ # type: 'post',
30
+ # attributes: {
31
+ # title: 'Title 1',
32
+ # date: '2015-12-20'
33
+ # },
34
+ # associations: {
35
+ # author: {
36
+ # data: {
37
+ # type: 'user',
38
+ # id: 2
39
+ # }
40
+ # },
41
+ # second_author: {
42
+ # data: nil
43
+ # },
44
+ # comments: {
45
+ # data: [{
46
+ # type: 'comment',
47
+ # id: 3
48
+ # },{
49
+ # type: 'comment',
50
+ # id: 4
51
+ # }]
52
+ # }
53
+ # }
54
+ # }
55
+ # }
56
+ #
57
+ # parse(document) #=>
58
+ # # {
59
+ # # title: 'Title 1',
60
+ # # date: '2015-12-20',
61
+ # # author_id: 2,
62
+ # # second_author_id: nil
63
+ # # comment_ids: [3, 4]
64
+ # # }
65
+ #
66
+ # parse(document, only: [:title, :date, :author],
67
+ # keys: { date: :published_at },
68
+ # polymorphic: [:author]) #=>
69
+ # # {
70
+ # # title: 'Title 1',
71
+ # # published_at: '2015-12-20',
72
+ # # author_id: '2',
73
+ # # author_type: 'people'
74
+ # # }
75
+ #
76
+ def parse!(document, options = {})
77
+ parse(document, options) do |invalid_payload, reason|
78
+ fail InvalidDocument, "Invalid payload (#{reason}): #{invalid_payload}"
79
+ end
80
+ end
81
+
82
+ # Same as parse!, but returns an empty hash instead of raising InvalidDocument
83
+ # on invalid payloads.
84
+ def parse(document, options = {})
85
+ document = document.dup.permit!.to_h if document.is_a?(ActionController::Parameters)
86
+
87
+ validate_payload(document) do |invalid_document, reason|
88
+ yield invalid_document, reason if block_given?
89
+ return {}
90
+ end
91
+
92
+ primary_data = document['data']
93
+ attributes = primary_data['attributes'] || {}
94
+ attributes['id'] = primary_data['id'] if primary_data['id']
95
+ relationships = primary_data['relationships'] || {}
96
+
97
+ filter_fields(attributes, options)
98
+ filter_fields(relationships, options)
99
+
100
+ hash = {}
101
+ hash.merge!(parse_attributes(attributes, options))
102
+ hash.merge!(parse_relationships(relationships, options))
103
+
104
+ hash
105
+ end
106
+
107
+ # Checks whether a payload is compliant with the JSON API spec.
108
+ #
109
+ # @api private
110
+ # rubocop:disable Metrics/CyclomaticComplexity
111
+ def validate_payload(payload)
112
+ unless payload.is_a?(Hash)
113
+ yield payload, 'Expected hash'
114
+ return
115
+ end
116
+
117
+ primary_data = payload['data']
118
+ unless primary_data.is_a?(Hash)
119
+ yield payload, { data: 'Expected hash' }
120
+ return
121
+ end
122
+
123
+ attributes = primary_data['attributes'] || {}
124
+ unless attributes.is_a?(Hash)
125
+ yield payload, { data: { attributes: 'Expected hash or nil' } }
126
+ return
127
+ end
128
+
129
+ relationships = primary_data['relationships'] || {}
130
+ unless relationships.is_a?(Hash)
131
+ yield payload, { data: { relationships: 'Expected hash or nil' } }
132
+ return
133
+ end
134
+
135
+ relationships.each do |(key, value)|
136
+ unless value.is_a?(Hash) && value.key?('data')
137
+ yield payload, { data: { relationships: { key => 'Expected hash with :data key' } } }
138
+ end
139
+ end
140
+ end
141
+ # rubocop:enable Metrics/CyclomaticComplexity
142
+
143
+ # @api private
144
+ def filter_fields(fields, options)
145
+ if (only = options[:only])
146
+ fields.slice!(*Array(only).map(&:to_s))
147
+ elsif (except = options[:except])
148
+ fields.except!(*Array(except).map(&:to_s))
149
+ end
150
+ end
151
+
152
+ # @api private
153
+ def field_key(field, options)
154
+ (options[:keys] || {}).fetch(field.to_sym, field).to_sym
155
+ end
156
+
157
+ # @api private
158
+ def parse_attributes(attributes, options)
159
+ attributes
160
+ .map { |(k, v)| { field_key(k, options) => v } }
161
+ .reduce({}, :merge)
162
+ end
163
+
164
+ # Given an association name, and a relationship data attribute, build a hash
165
+ # mapping the corresponding ActiveRecord attribute to the corresponding value.
166
+ #
167
+ # @example
168
+ # parse_relationship(:comments, [{ 'id' => '1', 'type' => 'comments' },
169
+ # { 'id' => '2', 'type' => 'comments' }],
170
+ # {})
171
+ # # => { :comment_ids => ['1', '2'] }
172
+ # parse_relationship(:author, { 'id' => '1', 'type' => 'users' }, {})
173
+ # # => { :author_id => '1' }
174
+ # parse_relationship(:author, nil, {})
175
+ # # => { :author_id => nil }
176
+ # @param [Symbol] assoc_name
177
+ # @param [Hash] assoc_data
178
+ # @param [Hash] options
179
+ # @return [Hash{Symbol, Object}]
180
+ #
181
+ # @api private
182
+ def parse_relationship(assoc_name, assoc_data, options)
183
+ prefix_key = field_key(assoc_name, options).to_s.singularize
184
+ hash =
185
+ if assoc_data.is_a?(Array)
186
+ { "#{prefix_key}_ids".to_sym => assoc_data.map { |ri| ri['id'] } }
187
+ else
188
+ { "#{prefix_key}_id".to_sym => assoc_data ? assoc_data['id'] : nil }
189
+ end
190
+
191
+ polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym)
192
+ hash.merge!("#{prefix_key}_type".to_sym => assoc_data['type']) if polymorphic
193
+
194
+ hash
195
+ end
196
+
197
+ # @api private
198
+ def parse_relationships(relationships, options)
199
+ relationships
200
+ .map { |(k, v)| parse_relationship(k, v['data'], options) }
201
+ .reduce({}, :merge)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
@@ -1,4 +1,8 @@
1
- class ActiveModel::Serializer::Adapter::JsonApi::FragmentCache
1
+ module ActiveModel
2
+ class Serializer
3
+ module Adapter
4
+ class JsonApi
5
+ class FragmentCache
2
6
  def fragment_cache(root, cached_hash, non_cached_hash)
3
7
  hash = {}
4
8
  core_cached = cached_hash.first
@@ -10,4 +14,8 @@ class ActiveModel::Serializer::Adapter::JsonApi::FragmentCache
10
14
 
11
15
  hash.deep_merge no_root_non_cache.deep_merge no_root_cache
12
16
  end
17
+ end
18
+ end
19
+ end
20
+ end
13
21
  end
@@ -0,0 +1,44 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ module Adapter
4
+ class JsonApi
5
+ class Link
6
+ def initialize(serializer, value)
7
+ @object = serializer.object
8
+ @scope = serializer.scope
9
+
10
+ # Use the return value of the block unless it is nil.
11
+ if value.respond_to?(:call)
12
+ @value = instance_eval(&value)
13
+ else
14
+ @value = value
15
+ end
16
+ end
17
+
18
+ def href(value)
19
+ @href = value
20
+ nil
21
+ end
22
+
23
+ def meta(value)
24
+ @meta = value
25
+ nil
26
+ end
27
+
28
+ def as_json
29
+ return @value if @value
30
+
31
+ hash = { href: @href }
32
+ hash.merge!(meta: @meta) if @meta
33
+
34
+ hash
35
+ end
36
+
37
+ protected
38
+
39
+ attr_reader :object, :scope
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,4 +1,8 @@
1
- class ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks
1
+ module ActiveModel
2
+ class Serializer
3
+ module Adapter
4
+ class JsonApi < Base
5
+ class PaginationLinks
2
6
  FIRST_PAGE = 1
3
7
 
4
8
  attr_reader :collection, :context
@@ -37,14 +41,18 @@ class ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks
37
41
  end
38
42
 
39
43
  def url(options)
40
- @url ||= options.fetch(:links, {}).fetch(:self, nil) || original_url
44
+ @url ||= options.fetch(:links, {}).fetch(:self, nil) || request_url
41
45
  end
42
46
 
43
- def original_url
44
- @original_url ||= context.original_url[/\A[^?]+/]
47
+ def request_url
48
+ @request_url ||= context.request_url
45
49
  end
46
50
 
47
51
  def query_parameters
48
52
  @query_parameters ||= context.query_parameters
49
53
  end
54
+ end
55
+ end
56
+ end
57
+ end
50
58
  end
@@ -1,5 +1,11 @@
1
- class ActiveModel::Serializer::Adapter::Null < ActiveModel::Serializer::Adapter
1
+ module ActiveModel
2
+ class Serializer
3
+ module Adapter
4
+ class Null < Base
2
5
  def serializable_hash(options = nil)
3
6
  {}
4
7
  end
8
+ end
9
+ end
10
+ end
5
11
  end
@@ -1,40 +1,9 @@
1
- module ActiveModel
2
- class Serializer
3
- class ArraySerializer
4
- NoSerializerError = Class.new(StandardError)
5
- include Enumerable
6
- delegate :each, to: :@serializers
7
-
8
- attr_reader :object, :root, :meta, :meta_key
9
-
10
- def initialize(resources, options = {})
11
- @root = options[:root]
12
- @object = resources
13
- @serializers = resources.map do |resource|
14
- serializer_class = options.fetch(:serializer) {
15
- ActiveModel::Serializer.serializer_for(resource)
16
- }
17
-
18
- if serializer_class.nil?
19
- fail NoSerializerError, "No serializer found for resource: #{resource.inspect}"
20
- else
21
- serializer_class.new(resource, options.except(:serializer))
22
- end
23
- end
24
- @meta = options[:meta]
25
- @meta_key = options[:meta_key]
26
- end
27
-
28
- def json_key
29
- key = root || @serializers.first.try(:json_key) || object.try(:name).try(:underscore)
30
- key.try(:pluralize)
31
- end
32
-
33
- def paginated?
34
- object.respond_to?(:current_page) &&
35
- object.respond_to?(:total_pages) &&
36
- object.respond_to?(:size)
37
- end
1
+ require 'active_model/serializer/collection_serializer'
2
+ class ActiveModel::Serializer
3
+ class ArraySerializer < CollectionSerializer
4
+ def initialize(*)
5
+ warn "Calling deprecated ArraySerializer in #{caller[0..2].join(', ')}. Please use CollectionSerializer"
6
+ super
38
7
  end
39
8
  end
40
9
  end
@@ -10,11 +10,15 @@ module ActiveModel
10
10
  module Associations
11
11
  extend ActiveSupport::Concern
12
12
 
13
- included do |base|
14
- class << base
15
- attr_accessor :_reflections
13
+ DEFAULT_INCLUDE_TREE = ActiveModel::Serializer::IncludeTree.from_string('*')
14
+
15
+ included do
16
+ with_options instance_writer: false, instance_reader: true do |serializer|
17
+ serializer.class_attribute :_reflections
18
+ self._reflections ||= []
16
19
  end
17
20
 
21
+ extend ActiveSupport::Autoload
18
22
  autoload :Association
19
23
  autoload :Reflection
20
24
  autoload :SingularReflection
@@ -26,7 +30,8 @@ module ActiveModel
26
30
 
27
31
  module ClassMethods
28
32
  def inherited(base)
29
- base._reflections = self._reflections.try(:dup) || []
33
+ super
34
+ base._reflections = _reflections.dup
30
35
  end
31
36
 
32
37
  # @param [Symbol] name of the association
@@ -36,8 +41,8 @@ module ActiveModel
36
41
  # @example
37
42
  # has_many :comments, serializer: CommentSummarySerializer
38
43
  #
39
- def has_many(name, options = {})
40
- associate HasManyReflection.new(name, options)
44
+ def has_many(name, options = {}, &block)
45
+ associate(HasManyReflection.new(name, options, block))
41
46
  end
42
47
 
43
48
  # @param [Symbol] name of the association
@@ -47,8 +52,8 @@ module ActiveModel
47
52
  # @example
48
53
  # belongs_to :author, serializer: AuthorSerializer
49
54
  #
50
- def belongs_to(name, options = {})
51
- associate BelongsToReflection.new(name, options)
55
+ def belongs_to(name, options = {}, &block)
56
+ associate(BelongsToReflection.new(name, options, block))
52
57
  end
53
58
 
54
59
  # @param [Symbol] name of the association
@@ -58,8 +63,8 @@ module ActiveModel
58
63
  # @example
59
64
  # has_one :author, serializer: AuthorSerializer
60
65
  #
61
- def has_one(name, options = {})
62
- associate HasOneReflection.new(name, options)
66
+ def has_one(name, options = {}, &block)
67
+ associate(HasOneReflection.new(name, options, block))
63
68
  end
64
69
 
65
70
  private
@@ -71,24 +76,22 @@ module ActiveModel
71
76
  # @api private
72
77
  #
73
78
  def associate(reflection)
74
- self._reflections = _reflections.dup
75
-
76
- define_method reflection.name do
77
- object.send reflection.name
78
- end unless method_defined?(reflection.name)
79
-
80
79
  self._reflections << reflection
81
80
  end
82
81
  end
83
82
 
83
+ # @param [IncludeTree] include_tree (defaults to all associations when not provided)
84
84
  # @return [Enumerator<Association>]
85
85
  #
86
- def associations
86
+ def associations(include_tree = DEFAULT_INCLUDE_TREE)
87
87
  return unless object
88
88
 
89
89
  Enumerator.new do |y|
90
90
  self.class._reflections.each do |reflection|
91
- y.yield reflection.build_association(self, options)
91
+ next if reflection.excluded?(self)
92
+ key = reflection.options.fetch(:key, reflection.name)
93
+ next unless include_tree.key?(key)
94
+ y.yield reflection.build_association(self, instance_options)
92
95
  end
93
96
  end
94
97
  end