lp-serializable 0.1.0 → 1.0.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.
@@ -0,0 +1 @@
1
+ 2.5.0
@@ -2,4 +2,6 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - 2.5.0
5
- before_install: gem install bundler -v 1.16.1
5
+ before_install: gem install bundler -v 1.16.3
6
+ ignore:
7
+ - spec/lib/object_serializer_performance.rb
@@ -1,12 +1,37 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lp-serializable (0.1.0)
5
- fast_jsonapi
4
+ lp-serializable (1.0.0)
5
+ activesupport (>= 4.2)
6
+ fast_jsonapi (>= 1.3)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
11
+ actionpack (5.2.0)
12
+ actionview (= 5.2.0)
13
+ activesupport (= 5.2.0)
14
+ rack (~> 2.0)
15
+ rack-test (>= 0.6.3)
16
+ rails-dom-testing (~> 2.0)
17
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
18
+ actionview (5.2.0)
19
+ activesupport (= 5.2.0)
20
+ builder (~> 3.1)
21
+ erubi (~> 1.4)
22
+ rails-dom-testing (~> 2.0)
23
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
24
+ active_model_serializers (0.10.7)
25
+ actionpack (>= 4.1, < 6)
26
+ activemodel (>= 4.1, < 6)
27
+ case_transform (>= 0.2)
28
+ jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
29
+ activemodel (5.2.0)
30
+ activesupport (= 5.2.0)
31
+ activerecord (5.2.0)
32
+ activemodel (= 5.2.0)
33
+ activesupport (= 5.2.0)
34
+ arel (>= 9.0)
10
35
  activesupport (5.2.0)
11
36
  concurrent-ruby (~> 1.0, >= 1.0.2)
12
37
  i18n (>= 0.7, < 2)
@@ -16,23 +41,58 @@ GEM
16
41
  bundler
17
42
  rake
18
43
  thor (>= 0.14.0)
44
+ arel (9.0.0)
45
+ benchmark-perf (0.2.1)
46
+ builder (3.2.3)
47
+ byebug (10.0.2)
48
+ case_transform (0.2)
49
+ activesupport
19
50
  coderay (1.1.2)
20
- concurrent-ruby (1.0.5)
51
+ concurrent-ruby (1.1.5)
52
+ crass (1.0.4)
21
53
  diff-lcs (1.3)
22
- fast_jsonapi (1.2)
54
+ erubi (1.8.0)
55
+ fast_jsonapi (1.5)
23
56
  activesupport (>= 4.2)
24
- i18n (1.0.1)
57
+ i18n (1.6.0)
25
58
  concurrent-ruby (~> 1.0)
59
+ jsonapi-deserializable (0.2.0)
60
+ jsonapi-rb (0.5.0)
61
+ jsonapi-deserializable (~> 0.2.0)
62
+ jsonapi-serializable (~> 0.3.0)
63
+ jsonapi-renderer (0.2.0)
64
+ jsonapi-serializable (0.3.0)
65
+ jsonapi-renderer (~> 0.2.0)
66
+ jsonapi-serializers (1.0.1)
67
+ activesupport
68
+ loofah (2.2.3)
69
+ crass (~> 1.0.2)
70
+ nokogiri (>= 1.5.9)
26
71
  method_source (0.9.0)
72
+ mini_portile2 (2.4.0)
27
73
  minitest (5.11.3)
74
+ nokogiri (1.10.2)
75
+ mini_portile2 (~> 2.4.0)
76
+ oj (3.6.3)
28
77
  pry (0.11.3)
29
78
  coderay (~> 1.1.0)
30
79
  method_source (~> 0.9.0)
80
+ rack (2.0.6)
81
+ rack-test (1.0.0)
82
+ rack (>= 1.0, < 3)
83
+ rails-dom-testing (2.0.3)
84
+ activesupport (>= 4.2.0)
85
+ nokogiri (>= 1.6)
86
+ rails-html-sanitizer (1.0.4)
87
+ loofah (~> 2.2, >= 2.2.2)
31
88
  rake (10.5.0)
32
89
  rspec (3.7.0)
33
90
  rspec-core (~> 3.7.0)
34
91
  rspec-expectations (~> 3.7.0)
35
92
  rspec-mocks (~> 3.7.0)
93
+ rspec-benchmark (0.3.0)
94
+ benchmark-perf (~> 0.2.0)
95
+ rspec (>= 3.0.0, < 4.0.0)
36
96
  rspec-core (3.7.1)
37
97
  rspec-support (~> 3.7.0)
38
98
  rspec-expectations (3.7.0)
@@ -42,6 +102,7 @@ GEM
42
102
  diff-lcs (>= 1.2.0, < 2.0)
43
103
  rspec-support (~> 3.7.0)
44
104
  rspec-support (3.7.1)
105
+ sqlite3 (1.3.13)
45
106
  thor (0.20.0)
46
107
  thread_safe (0.3.6)
47
108
  tzinfo (1.2.5)
@@ -51,12 +112,20 @@ PLATFORMS
51
112
  ruby
52
113
 
53
114
  DEPENDENCIES
115
+ active_model_serializers (~> 0.10.7)
116
+ activerecord (>= 4.2)
54
117
  appraisal
55
118
  bundler (~> 1.16)
119
+ byebug
120
+ jsonapi-rb (~> 0.5.0)
121
+ jsonapi-serializers (~> 1.0.0)
56
122
  lp-serializable!
123
+ oj (~> 3.3)
57
124
  pry
58
125
  rake (~> 10.0)
59
126
  rspec (~> 3.0)
127
+ rspec-benchmark (~> 0.3.0)
128
+ sqlite3 (~> 1.3)
60
129
 
61
130
  BUNDLED WITH
62
- 1.16.1
131
+ 1.16.3
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Lp::Serializable
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/lp/serializable`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ When serializing with [fast_jsonapi](https://github.com/Netflix/fast_jsonapi), data is structured per the json-api [specs](http://jsonapi.org/format/).
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ lp-serializable is a thin wrapper around fast_jsonapi serialization, producting AMS style output.
6
+
7
+ lp-serializable is intended to be used in Rails controllers.
6
8
 
7
9
  ## Installation
8
10
 
@@ -22,7 +24,170 @@ Or install it yourself as:
22
24
 
23
25
  ## Usage
24
26
 
25
- TODO: Write usage instructions here
27
+ **Controller Definition**
28
+
29
+ ```ruby
30
+ class ApplicationController < ActionController::Base
31
+ include Lp::Serializable
32
+ end
33
+
34
+ class MoviesController < ApplicationController
35
+ def index
36
+ movies = Movie.all
37
+ movies_hash = serializable_collection(movies, 'Movie')
38
+ render json: movies_hash
39
+ end
40
+
41
+ def show
42
+ movie = Movie.find(params[:id])
43
+ movie_hash = serializable(movie)
44
+ render json: movie_hash
45
+ end
46
+ end
47
+ ```
48
+
49
+ **Serializer Definition**
50
+
51
+ ```ruby
52
+ class MovieSerializer
53
+ include FastJsonapi::ObjectSerializer
54
+
55
+ attributes :name
56
+
57
+ attribute :year, if: Proc.new { |object| object.year.present? }
58
+
59
+ attribute :last_updated do |object|
60
+ object.updated_at
61
+ end
62
+
63
+ has_many :actors
64
+ belongs_to :owner
65
+ end
66
+
67
+ class ActorSerializer
68
+ include FastJsonapi::ObjectSerializer
69
+
70
+ attributes :id
71
+ end
72
+
73
+ class OwnerSerializer
74
+ include FastJsonapi::ObjectSerializer
75
+
76
+ attributes :id
77
+ end
78
+ ```
79
+
80
+ ## Object Serialization
81
+ **Sample Object**
82
+
83
+ ```ruby
84
+ movie = Movie.new
85
+ movie.id = 232
86
+ movie.name = 'test movie'
87
+ movie.actor_ids = [1, 2, 3]
88
+ movie.owner_id = 3
89
+ movie.movie_type_id = 1
90
+ movie
91
+ ```
92
+
93
+ **Return a hash**
94
+ ```ruby
95
+ hash = serializable(movie)
96
+ ```
97
+
98
+ **Output**
99
+
100
+ ```json
101
+ {
102
+ "data": {
103
+ "id": "3",
104
+ "type": "movie",
105
+ "name": "test movie",
106
+ "last_updated": "2019-04-26 18:55:46 UTC",
107
+ "actors": [
108
+ {
109
+ "id": "1",
110
+ "type": "actor"
111
+ },
112
+ {
113
+ "id": "2",
114
+ "type": "actor"
115
+ }
116
+ ],
117
+ "owner": {
118
+ "id": "3",
119
+ "type": "user"
120
+ }
121
+ }
122
+ }
123
+
124
+ ```
125
+
126
+ For more information on configuration, refer to [fast_jsonapi](https://github.com/Netflix/fast_jsonapi#customizable-options) documentation.
127
+
128
+ ## Deeply Nested Serialization Pattern
129
+
130
+ Fastjson API does not support serialization of deeply nested resources.
131
+
132
+ To get around this, extend `Lp::Serializable` in your serializers:
133
+
134
+ ```ruby
135
+ class MovieSerializer
136
+ include FastJsonapi::ObjectSerializer
137
+ extend Lp::Serializable
138
+
139
+ ...
140
+ end
141
+ ```
142
+
143
+ Define custom attributes for relationships, instead of defining them via fastjson_api:
144
+
145
+ ```ruby
146
+ class MovieSerializer
147
+ include FastJsonapi::ObjectSerializer
148
+ extend Lp::Serializable
149
+
150
+ attribute :actors do |object|
151
+ collection = object.actors
152
+ serializer = 'Actor'
153
+ serializable_collection(collection, serializer, nested: true)
154
+ end
155
+ end
156
+ ```
157
+
158
+ Attribute `:actors` will trigger `ActorSerializer` to serialize the actors collection. Consequently, any relationships defined in `ActorSerializer` via custom attributes and serialized with `serializable_` methods (using the `nested: true` option) will be appropriately nested.
159
+
160
+ ## Custom Serializer Class
161
+
162
+ Use `#serializable_class` to serialize with a custom class:
163
+
164
+ ```ruby
165
+ def show
166
+ movie = Movie.find(params[:id])
167
+ # Will serialize with FilmSerializer instead of MovieSerializer
168
+ movie_hash = serializable_class(movie, 'Film')
169
+ render json: movie_hash
170
+ end
171
+ ```
172
+
173
+ ## Options Support
174
+
175
+ Supported options include:
176
+
177
+ - `:fields` ([Sparse Fieldsets](https://github.com/Netflix/fast_jsonapi#sparse-fieldsets))
178
+ - `:params` ([Params](https://github.com/Netflix/fast_jsonapi#params))
179
+ - [Conditional Attributes](https://github.com/Netflix/fast_jsonapi#conditional-attributes)
180
+
181
+ Other options are "supported" but may yeild unexpected results, as Serializable's hash flattening prioritizes deeply nested data structures.
182
+
183
+ `:is_collection` is baked into Seriazable methods for accurate detection of collections or singular resources.
184
+
185
+ ## Aliases
186
+
187
+ - `serialize_and_flatten()` = `serializable()`
188
+ - `serialize_and_flatten_with_class_name()` = `serializable_class()`
189
+ - `serialize_and_flatten_collection()` = `serializable_collection()`
190
+
26
191
 
27
192
  ## Development
28
193
 
@@ -32,7 +197,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
197
 
33
198
  ## Contributing
34
199
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/lp-serializable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
200
+ Bug reports and pull requests are welcome on GitHub at https://github.com/LaunchPadLab/lp-serializable. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
201
 
37
202
  ## License
38
203
 
@@ -40,4 +205,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
205
 
41
206
  ## Code of Conduct
42
207
 
43
- Everyone interacting in the Lp::Serializable project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/lp-serializable/blob/master/CODE_OF_CONDUCT.md).
208
+ Everyone interacting in the Lp::Serializable project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/LaunchPadLab/lp-serializable/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ # Usage:
6
+ # class Movie
7
+ # def to_json(payload)
8
+ # FastJsonapi::MultiToJson.to_json(payload)
9
+ # end
10
+ # end
11
+ module FastJsonapi
12
+ module MultiToJson
13
+ # Result object pattern is from https://johnnunemaker.com/resilience-in-ruby/
14
+ # e.g. https://github.com/github/github-ds/blob/fbda5389711edfb4c10b6c6bad19311dfcb1bac1/lib/github/result.rb
15
+ class Result
16
+ def initialize(*rescued_exceptions)
17
+ @rescued_exceptions = if rescued_exceptions.empty?
18
+ [StandardError]
19
+ else
20
+ rescued_exceptions
21
+ end
22
+
23
+ @value = yield
24
+ @error = nil
25
+ rescue *rescued_exceptions => e
26
+ @error = e
27
+ end
28
+
29
+ def ok?
30
+ @error.nil?
31
+ end
32
+
33
+ def value!
34
+ if ok?
35
+ @value
36
+ else
37
+ raise @error
38
+ end
39
+ end
40
+
41
+ def rescue
42
+ return self if ok?
43
+
44
+ Result.new(*@rescued_exceptions) { yield(@error) }
45
+ end
46
+ end
47
+
48
+ def self.logger(device=nil)
49
+ return @logger = Logger.new(device) if device
50
+ @logger ||= Logger.new(IO::NULL)
51
+ end
52
+
53
+ # Encoder-compatible with default MultiJSON adapters and defaults
54
+ def self.to_json_method
55
+ encode_method = String.new(%(def _fast_to_json(object)\n ))
56
+ encode_method << Result.new(LoadError) {
57
+ require 'oj'
58
+ %(::Oj.dump(object, mode: :compat, time_format: :ruby, use_to_json: true))
59
+ }.rescue {
60
+ require 'yajl'
61
+ %(::Yajl::Encoder.encode(object))
62
+ }.rescue {
63
+ require 'jrjackson' unless defined?(::JrJackson)
64
+ %(::JrJackson::Json.dump(object))
65
+ }.rescue {
66
+ require 'json'
67
+ %(JSON.fast_generate(object, create_additions: false, quirks_mode: true))
68
+ }.rescue {
69
+ require 'gson'
70
+ %(::Gson::Encoder.new({}).encode(object))
71
+ }.rescue {
72
+ require 'active_support/json/encoding'
73
+ %(::ActiveSupport::JSON.encode(object))
74
+ }.rescue {
75
+ warn "No JSON encoder found. Falling back to `object.to_json`"
76
+ %(object.to_json)
77
+ }.value!
78
+ encode_method << "\nend"
79
+ end
80
+
81
+ def self.to_json(object)
82
+ _fast_to_json(object)
83
+ rescue NameError
84
+ define_to_json(FastJsonapi::MultiToJson)
85
+ _fast_to_json(object)
86
+ end
87
+
88
+ def self.define_to_json(receiver)
89
+ cl = caller_locations[0]
90
+ method_body = to_json_method
91
+ logger.debug { "Defining #{receiver}._fast_to_json as #{method_body.inspect}" }
92
+ receiver.instance_eval method_body, cl.absolute_path, cl.lineno
93
+ end
94
+
95
+ def self.reset_to_json!
96
+ undef :_fast_to_json if method_defined?(:_fast_to_json)
97
+ logger.debug { "Undefining #{receiver}._fast_to_json" }
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,261 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object'
4
+ require 'active_support/concern'
5
+ require 'active_support/inflector'
6
+ require 'fast_jsonapi/serialization_core'
7
+ require 'fast_jsonapi/attribute'
8
+
9
+ module FastJsonapi
10
+ module ObjectSerializer
11
+ extend ActiveSupport::Concern
12
+ include SerializationCore
13
+
14
+ SERIALIZABLE_HASH_NOTIFICATION = 'render.fast_jsonapi.serializable_hash'
15
+ SERIALIZED_JSON_NOTIFICATION = 'render.fast_jsonapi.serialized_json'
16
+
17
+ included do
18
+ # Set record_type based on the name of the serializer class
19
+ set_type(reflected_record_type) if reflected_record_type
20
+ end
21
+
22
+ def initialize(resource, options = {})
23
+ process_options(options)
24
+
25
+ @resource = resource
26
+ end
27
+
28
+ def serializable_hash
29
+ return hash_for_collection if is_collection?(@resource)
30
+
31
+ hash_for_one_record
32
+ end
33
+ alias_method :to_hash, :serializable_hash
34
+
35
+ def hash_for_one_record
36
+ serializable_hash = { data: nil }
37
+ serializable_hash[:meta] = @meta if @meta.present?
38
+ serializable_hash[:links] = @links if @links.present?
39
+
40
+ return serializable_hash unless @resource
41
+
42
+ serializable_hash[:data] = self.class.record_hash(@resource, @params)
43
+ serializable_hash[:included] = self.class.get_included_records(@resource, @includes, @known_included_objects, @params) if @includes.present?
44
+ serializable_hash
45
+ end
46
+
47
+ def hash_for_collection
48
+ serializable_hash = {}
49
+
50
+ data = []
51
+ included = []
52
+ @resource.each do |record|
53
+ data << self.class.record_hash(record, @params)
54
+ included.concat self.class.get_included_records(record, @includes, @known_included_objects, @params) if @includes.present?
55
+ end
56
+
57
+ serializable_hash[:data] = data
58
+ serializable_hash[:included] = included if @includes.present?
59
+ serializable_hash[:meta] = @meta if @meta.present?
60
+ serializable_hash[:links] = @links if @links.present?
61
+ serializable_hash
62
+ end
63
+
64
+ def serialized_json
65
+ self.class.to_json(serializable_hash)
66
+ end
67
+
68
+ private
69
+
70
+ def process_options(options)
71
+ return if options.blank?
72
+
73
+ @known_included_objects = {}
74
+ @meta = options[:meta]
75
+ @links = options[:links]
76
+ @params = options[:params] || {}
77
+ raise ArgumentError.new("`params` option passed to serializer must be a hash") unless @params.is_a?(Hash)
78
+
79
+ if options[:include].present?
80
+ @includes = options[:include].delete_if(&:blank?).map(&:to_sym)
81
+ self.class.validate_includes!(@includes)
82
+ end
83
+ end
84
+
85
+ def is_collection?(resource)
86
+ resource.respond_to?(:each) && !resource.respond_to?(:each_pair)
87
+ end
88
+
89
+ class_methods do
90
+
91
+ def inherited(subclass)
92
+ super(subclass)
93
+ subclass.attributes_to_serialize = attributes_to_serialize.dup if attributes_to_serialize.present?
94
+ subclass.relationships_to_serialize = relationships_to_serialize.dup if relationships_to_serialize.present?
95
+ subclass.cachable_relationships_to_serialize = cachable_relationships_to_serialize.dup if cachable_relationships_to_serialize.present?
96
+ subclass.uncachable_relationships_to_serialize = uncachable_relationships_to_serialize.dup if uncachable_relationships_to_serialize.present?
97
+ subclass.transform_method = transform_method
98
+ subclass.cache_length = cache_length
99
+ subclass.race_condition_ttl = race_condition_ttl
100
+ subclass.data_links = data_links
101
+ subclass.cached = cached
102
+ end
103
+
104
+ def reflected_record_type
105
+ return @reflected_record_type if defined?(@reflected_record_type)
106
+
107
+ @reflected_record_type ||= begin
108
+ if self.name.end_with?('Serializer')
109
+ self.name.split('::').last.chomp('Serializer').underscore.to_sym
110
+ end
111
+ end
112
+ end
113
+
114
+ def set_key_transform(transform_name)
115
+ mapping = {
116
+ camel: :camelize,
117
+ camel_lower: [:camelize, :lower],
118
+ dash: :dasherize,
119
+ underscore: :underscore
120
+ }
121
+ self.transform_method = mapping[transform_name.to_sym]
122
+ end
123
+
124
+ def run_key_transform(input)
125
+ if self.transform_method.present?
126
+ input.to_s.send(*@transform_method).to_sym
127
+ else
128
+ input.to_sym
129
+ end
130
+ end
131
+
132
+ def use_hyphen
133
+ warn('DEPRECATION WARNING: use_hyphen is deprecated and will be removed from fast_jsonapi 2.0 use (set_key_transform :dash) instead')
134
+ set_key_transform :dash
135
+ end
136
+
137
+ def set_type(type_name)
138
+ self.record_type = run_key_transform(type_name)
139
+ end
140
+
141
+ def set_id(id_name)
142
+ self.record_id = id_name
143
+ end
144
+
145
+ def cache_options(cache_options)
146
+ self.cached = cache_options[:enabled] || false
147
+ self.cache_length = cache_options[:cache_length] || 5.minutes
148
+ self.race_condition_ttl = cache_options[:race_condition_ttl] || 5.seconds
149
+ end
150
+
151
+ def attributes(*attributes_list, &block)
152
+ attributes_list = attributes_list.first if attributes_list.first.class.is_a?(Array)
153
+ options = attributes_list.last.is_a?(Hash) ? attributes_list.pop : {}
154
+ self.attributes_to_serialize = {} if self.attributes_to_serialize.nil?
155
+
156
+ attributes_list.each do |attr_name|
157
+ method_name = attr_name
158
+ key = run_key_transform(method_name)
159
+ attributes_to_serialize[key] = Attribute.new(
160
+ key: key,
161
+ method: block || method_name,
162
+ options: options
163
+ )
164
+ end
165
+ end
166
+
167
+ alias_method :attribute, :attributes
168
+
169
+ def add_relationship(name, relationship)
170
+ self.relationships_to_serialize = {} if relationships_to_serialize.nil?
171
+ self.cachable_relationships_to_serialize = {} if cachable_relationships_to_serialize.nil?
172
+ self.uncachable_relationships_to_serialize = {} if uncachable_relationships_to_serialize.nil?
173
+
174
+ if !relationship[:cached]
175
+ self.uncachable_relationships_to_serialize[name] = relationship
176
+ else
177
+ self.cachable_relationships_to_serialize[name] = relationship
178
+ end
179
+ self.relationships_to_serialize[name] = relationship
180
+ end
181
+
182
+ def has_many(relationship_name, options = {}, &block)
183
+ name = relationship_name.to_sym
184
+ hash = create_relationship_hash(relationship_name, :has_many, options, block)
185
+ add_relationship(name, hash)
186
+ end
187
+
188
+ def has_one(relationship_name, options = {}, &block)
189
+ name = relationship_name.to_sym
190
+ hash = create_relationship_hash(relationship_name, :has_one, options, block)
191
+ add_relationship(name, hash)
192
+ end
193
+
194
+ def belongs_to(relationship_name, options = {}, &block)
195
+ name = relationship_name.to_sym
196
+ hash = create_relationship_hash(relationship_name, :belongs_to, options, block)
197
+ add_relationship(name, hash)
198
+ end
199
+
200
+ def create_relationship_hash(base_key, relationship_type, options, block)
201
+ name = base_key.to_sym
202
+ if relationship_type == :has_many
203
+ base_serialization_key = base_key.to_s.singularize
204
+ base_key_sym = base_serialization_key.to_sym
205
+ id_postfix = '_ids'
206
+ else
207
+ base_serialization_key = base_key
208
+ base_key_sym = name
209
+ id_postfix = '_id'
210
+ end
211
+ {
212
+ key: options[:key] || run_key_transform(base_key),
213
+ name: name,
214
+ id_method_name: options[:id_method_name] || "#{base_serialization_key}#{id_postfix}".to_sym,
215
+ record_type: options[:record_type] || run_key_transform(base_key_sym),
216
+ object_method_name: options[:object_method_name] || name,
217
+ object_block: block,
218
+ serializer: compute_serializer_name(options[:serializer] || base_key_sym),
219
+ relationship_type: relationship_type,
220
+ cached: options[:cached] || false,
221
+ polymorphic: fetch_polymorphic_option(options)
222
+ }
223
+ end
224
+
225
+ def compute_serializer_name(serializer_key)
226
+ return serializer_key unless serializer_key.is_a? Symbol
227
+ namespace = self.name.gsub(/()?\w+Serializer$/, '')
228
+ serializer_name = serializer_key.to_s.classify + 'Serializer'
229
+ (namespace + serializer_name).to_sym
230
+ end
231
+
232
+ def fetch_polymorphic_option(options)
233
+ option = options[:polymorphic]
234
+ return false unless option.present?
235
+ return option if option.respond_to? :keys
236
+ {}
237
+ end
238
+
239
+ def link(link_name, link_method_name = nil, &block)
240
+ self.data_links = {} if self.data_links.nil?
241
+ link_method_name = link_name if link_method_name.nil?
242
+ key = run_key_transform(link_name)
243
+ self.data_links[key] = block || link_method_name
244
+ end
245
+
246
+ def validate_includes!(includes)
247
+ return if includes.blank?
248
+
249
+ includes.detect do |include_item|
250
+ klass = self
251
+ parse_include_item(include_item).each do |parsed_include|
252
+ relationship_to_include = klass.relationships_to_serialize[parsed_include]
253
+ raise ArgumentError, "#{parsed_include} is not specified as a relationship on #{klass.name}" unless relationship_to_include
254
+ raise NotImplementedError if relationship_to_include[:polymorphic].is_a?(Hash)
255
+ klass = relationship_to_include[:serializer].to_s.constantize
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end