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.
- checksums.yaml +4 -4
- data/.rubocop.yml +650 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -1
- data/Gemfile.lock +75 -6
- data/README.md +170 -5
- data/lib/fast_jsonapi/multi_to_json.rb +100 -0
- data/lib/fast_jsonapi/object_serializer.rb +261 -0
- data/lib/fast_jsonapi/serialization_core.rb +189 -0
- data/lib/lp/serializable.rb +29 -17
- data/lib/lp/serializable/exceptions.rb +11 -0
- data/lib/lp/serializable/strategies.rb +12 -8
- data/lib/lp/serializable/utilities.rb +25 -8
- data/lib/lp/serializable/version.rb +2 -2
- data/lp-serializable.gemspec +12 -3
- metadata +139 -6
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5.0
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,37 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
lp-serializable (
|
5
|
-
|
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.
|
51
|
+
concurrent-ruby (1.1.5)
|
52
|
+
crass (1.0.4)
|
21
53
|
diff-lcs (1.3)
|
22
|
-
|
54
|
+
erubi (1.8.0)
|
55
|
+
fast_jsonapi (1.5)
|
23
56
|
activesupport (>= 4.2)
|
24
|
-
i18n (1.0
|
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.
|
131
|
+
1.16.3
|
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# Lp::Serializable
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
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/
|
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/
|
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
|