standardapi 6.1.0 → 7.1.1
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/README.md +80 -58
- data/lib/standard_api/access_control_list.rb +40 -6
- data/lib/standard_api/controller.rb +96 -28
- data/lib/standard_api/helpers.rb +22 -22
- data/lib/standard_api/middleware.rb +5 -0
- data/lib/standard_api/railtie.rb +17 -0
- data/lib/standard_api/route_helpers.rb +59 -9
- data/lib/standard_api/test_case/calculate_tests.rb +7 -6
- data/lib/standard_api/test_case/destroy_tests.rb +19 -7
- data/lib/standard_api/test_case/index_tests.rb +7 -13
- data/lib/standard_api/test_case/show_tests.rb +7 -7
- data/lib/standard_api/test_case/update_tests.rb +7 -6
- data/lib/standard_api/version.rb +1 -1
- data/lib/standard_api/views/application/_record.json.jbuilder +17 -6
- data/lib/standard_api/views/application/_record.streamer +4 -3
- data/lib/standard_api/views/application/_schema.json.jbuilder +20 -8
- data/lib/standard_api/views/application/_schema.streamer +22 -8
- data/lib/standard_api.rb +1 -0
- data/test/standard_api/caching_test.rb +2 -2
- data/test/standard_api/controller/include_test.rb +107 -0
- data/test/standard_api/controller/subresource_test.rb +157 -0
- data/test/standard_api/helpers_test.rb +9 -8
- data/test/standard_api/nested_attributes/belongs_to_test.rb +71 -0
- data/test/standard_api/nested_attributes/has_and_belongs_to_many_test.rb +70 -0
- data/test/standard_api/nested_attributes/has_many_test.rb +85 -0
- data/test/standard_api/nested_attributes/has_one_test.rb +71 -0
- data/test/standard_api/route_helpers_test.rb +56 -0
- data/test/standard_api/standard_api_test.rb +110 -50
- data/test/standard_api/test_app/app/controllers/acl/account_acl.rb +5 -1
- data/test/standard_api/test_app/app/controllers/acl/camera_acl.rb +7 -0
- data/test/standard_api/test_app/app/controllers/acl/photo_acl.rb +13 -0
- data/test/standard_api/test_app/app/controllers/acl/property_acl.rb +7 -1
- data/test/standard_api/test_app/controllers.rb +17 -0
- data/test/standard_api/test_app/models.rb +59 -2
- data/test/standard_api/test_app/test/factories.rb +3 -0
- data/test/standard_api/test_app/views/sessions/create.json.jbuilder +1 -0
- data/test/standard_api/test_app/views/sessions/create.streamer +3 -0
- data/test/standard_api/test_app.rb +13 -1
- data/test/standard_api/test_helper.rb +100 -7
- metadata +52 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf0f6e2d86659b79df8b56eb7b998118541facdc0fd516ad878f4afb0c531299
|
4
|
+
data.tar.gz: dcfebcfd01bd96dbaa8a4a2aef53ea1549c0820b7879845a2e812311efbd3556
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7b0189b209ce50457ba90a8f2c89653557f1f7ff61b0a955f479c40beefc8666f47e9dfe9c2e6b17193fc197510305ff442ea49c0bf31ab482011d729b7b09e
|
7
|
+
data.tar.gz: a31458fac536e7d0e074a3c4c5efee56123f264771316503df1ff69b8a4539e782e29c109d5c9f89a789d5f54b576300fde5de91852dc4a67a526b665bc793ff
|
data/README.md
CHANGED
@@ -1,36 +1,27 @@
|
|
1
1
|
# StandardAPI
|
2
2
|
|
3
|
-
StandardAPI makes it easy to
|
3
|
+
StandardAPI makes it easy to expose a [REST](https://en.wikipedia.org/wiki/Representational_state_transfer)
|
4
4
|
interface to your Rails models.
|
5
5
|
|
6
6
|
# Installation
|
7
7
|
|
8
8
|
gem install standardapi
|
9
9
|
|
10
|
-
In your Gemfile
|
10
|
+
In your `Gemfile`:
|
11
11
|
|
12
12
|
gem 'standardapi', require: 'standard_api'
|
13
13
|
|
14
|
-
|
14
|
+
Optionally in `config/application.rb`:
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
require 'rails/all'
|
19
|
-
require 'standard_api/middleware/query_encoding'
|
20
|
-
|
21
|
-
# Require the gems listed in Gemfile, including any gems
|
22
|
-
# you've limited to :test, :development, or :production.
|
23
|
-
Bundler.require(*Rails.groups)
|
24
|
-
|
25
|
-
module Tester
|
16
|
+
module MyApplication
|
26
17
|
class Application < Rails::Application
|
27
18
|
# Initialize configuration defaults for originally generated Rails version.
|
28
|
-
config.load_defaults
|
19
|
+
config.load_defaults 7.0
|
29
20
|
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
21
|
+
# QueryEncoding middleware intercepts and parses the query string
|
22
|
+
# as MessagePack if the `Query-Encoding` header is set to `application/msgpack`
|
23
|
+
# which allows GET request with types as opposed to all values being interpeted
|
24
|
+
# as strings
|
34
25
|
config.middleware.insert_after Rack::MethodOverride, StandardAPI::Middleware::QueryEncoding
|
35
26
|
end
|
36
27
|
end
|
@@ -41,66 +32,96 @@ StandardAPI is a module that can be included into any controller to expose a API
|
|
41
32
|
for. Alternatly, it can be included into `ApplicationController`, giving all
|
42
33
|
inherited controllers an exposed API.
|
43
34
|
|
44
|
-
class ApplicationController < ActiveController::Base
|
45
|
-
include StandardAPI::Controller
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
By default any paramaters passed to update and create are whitelisted with by
|
50
|
-
the method named after the model the controller represents. For example, the
|
51
|
-
following will only allow the `caption` attribute of the `Photo` model to be
|
52
|
-
updated.
|
53
|
-
|
54
35
|
class PhotosController < ApplicationController
|
55
36
|
include StandardAPI
|
56
37
|
|
38
|
+
# Allowed paramaters
|
39
|
+
# By default any paramaters passed to update and create are whitelisted by
|
40
|
+
# the method named after the model the controller represents. For example,
|
41
|
+
# the following will only allow the `caption` attribute of the `Photo`
|
42
|
+
# model to be set on update or create.
|
57
43
|
def photo_params
|
58
44
|
[:caption]
|
59
45
|
end
|
46
|
+
|
47
|
+
# Allowed orderings
|
48
|
+
# The ordering is whitelisted as well, you will mostly likely want to
|
49
|
+
# ensure indexes have been created on these columns. In this example the
|
50
|
+
# response can be ordered by any permutation of `id`, `created_at`, and
|
51
|
+
# `updated_at`.
|
52
|
+
def photo_orders
|
53
|
+
[:id, :created_at, :updated_at]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Allowed includes
|
57
|
+
# Similarly, the includes (including of relationships in the reponse) are
|
58
|
+
# whitelisted. Note how includes can also support nested includes. In this
|
59
|
+
# case when including the author, the photos that the author took can also
|
60
|
+
# be included.
|
61
|
+
def photo_includes
|
62
|
+
{ author: [:photos] }
|
63
|
+
end
|
60
64
|
end
|
61
65
|
|
62
|
-
|
63
|
-
method can be overridden. It simply returns a set of `StrongParameters`.
|
66
|
+
##### Access Control List
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
[:comment]
|
73
|
-
end
|
74
|
-
end
|
68
|
+
For greater control of the allowed paramaters and nesting of paramaters
|
69
|
+
`StandardAPI::AccessControlList` is available. To use it include it in your base
|
70
|
+
controller:
|
71
|
+
|
72
|
+
class ApplicationController
|
73
|
+
include StandardAPI::Control
|
74
|
+
include StandardAPI::AccessControlList
|
75
75
|
end
|
76
76
|
|
77
|
-
|
78
|
-
is whitelisted as well.
|
77
|
+
Then create an ACL file for each model you want in `app/controllers/acl`.
|
79
78
|
|
80
|
-
|
79
|
+
Taking the above example we would remove the `photo_*` methods and create the
|
80
|
+
following files:
|
81
81
|
|
82
|
-
|
83
|
-
including StandardAPI
|
82
|
+
`app/controllers/acl/photo_acl.rb`:
|
84
83
|
|
85
|
-
|
86
|
-
|
87
|
-
|
84
|
+
module PhotoACL
|
85
|
+
# Allowed attributes
|
86
|
+
def attributes
|
87
|
+
[ :caption ]
|
88
88
|
end
|
89
|
-
|
90
|
-
# Allowed
|
91
|
-
def
|
92
|
-
[
|
89
|
+
|
90
|
+
# Allowed saving / creating nested attributes
|
91
|
+
def nested
|
92
|
+
[ :camera ]
|
93
93
|
end
|
94
|
-
|
94
|
+
|
95
|
+
# Allowed orders
|
96
|
+
def orders
|
97
|
+
[ :id, :created_at, :updated_at ]
|
98
|
+
end
|
99
|
+
|
95
100
|
# Allowed includes
|
96
|
-
def
|
97
|
-
|
101
|
+
def includes
|
102
|
+
[ :author ]
|
98
103
|
end
|
104
|
+
end
|
99
105
|
|
106
|
+
`app/controllers/acl/author_acl.rb`:
|
107
|
+
|
108
|
+
module AuthorACL
|
109
|
+
def includes
|
110
|
+
[ :photos ]
|
111
|
+
end
|
100
112
|
end
|
101
113
|
|
102
|
-
|
103
|
-
|
114
|
+
All of these methods are optional and will be included in ApplicationController
|
115
|
+
for StandardAPI to determine allowed attributes, nested attributes, orders and
|
116
|
+
includes.
|
117
|
+
|
118
|
+
`includes` now returns a shallow Array, StandardAPI can how determine including
|
119
|
+
an `author` and the author's `photos` is allowed by looking at what includes are
|
120
|
+
allowed on photo and author.
|
121
|
+
|
122
|
+
The `nested` function tells StandardAPI what relations on `Photo` are allowed to
|
123
|
+
be set with the API and will determine what attributes are allowed by looking
|
124
|
+
for a `camera_acl` file.
|
104
125
|
|
105
126
|
# API Usage
|
106
127
|
Resources can be queried via REST style end points
|
@@ -208,7 +229,7 @@ And example contoller and it's tests.
|
|
208
229
|
# The mask is then applyed to all actions when querring ActiveRecord
|
209
230
|
# Will only allow photos that have id one. For more on the syntax see
|
210
231
|
# the activerecord-filter gem.
|
211
|
-
def
|
232
|
+
def mask_for(table_name)
|
212
233
|
{ id: 1 }
|
213
234
|
end
|
214
235
|
|
@@ -231,3 +252,4 @@ StandardAPI Resource Interface
|
|
231
252
|
| `/models?where[id][]=1&where[id][]=2` | `{ where: { id: [1,2] } }` | `SELECT * FROM models WHERE id IN (1, 2)` | `[{ id: 1 }, { id: 2 }]` |
|
232
253
|
|
233
254
|
|
255
|
+
|
@@ -56,7 +56,7 @@ module StandardAPI
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def filter_model_params(model_params, model, id: nil, allow_id: nil)
|
59
|
-
permitted_params = if self.respond_to?("#{model_name(model)}_attributes", true)
|
59
|
+
permitted_params = if model_params && self.respond_to?("#{model_name(model)}_attributes", true)
|
60
60
|
permits = self.send("#{model_name(model)}_attributes")
|
61
61
|
|
62
62
|
allow_id ? model_params.permit(permits, :id) : model_params.permit(permits)
|
@@ -67,7 +67,7 @@ module StandardAPI
|
|
67
67
|
if self.respond_to?("nested_#{model_name(model)}_attributes", true)
|
68
68
|
self.send("nested_#{model_name(model)}_attributes").each do |relation|
|
69
69
|
relation = model.reflect_on_association(relation)
|
70
|
-
attributes_key = "#{relation.name}
|
70
|
+
attributes_key = "#{relation.name}"
|
71
71
|
|
72
72
|
if model_params.has_key?(attributes_key)
|
73
73
|
filter_method = "filter_#{relation.klass.base_class.model_name.singular}_params"
|
@@ -77,15 +77,49 @@ module StandardAPI
|
|
77
77
|
permitted_params["#{relation.name.to_s.singularize}_ids"] = model_params[attributes_key].map{|a| a['id']}
|
78
78
|
elsif self.respond_to?(filter_method, true)
|
79
79
|
permitted_params[attributes_key] = if model_params[attributes_key].is_a?(Array)
|
80
|
-
model_params[attributes_key].map { |i|
|
80
|
+
models = relation.klass.find(model_params[attributes_key].map { |i| i['id'] }.compact)
|
81
|
+
model_params[attributes_key].map { |i|
|
82
|
+
i_params = self.send(filter_method, i, allow_id: true)
|
83
|
+
if i_params['id']
|
84
|
+
r = models.find { |r| r.id == i_params['id'] }
|
85
|
+
r.assign_attributes(i_params)
|
86
|
+
r
|
87
|
+
else
|
88
|
+
relation.klass.new(i_params)
|
89
|
+
end
|
90
|
+
}
|
81
91
|
else
|
82
|
-
self.send(filter_method, model_params[attributes_key], allow_id: true)
|
92
|
+
i_params = self.send(filter_method, model_params[attributes_key], allow_id: true)
|
93
|
+
if i_params['id']
|
94
|
+
r = relation.klass.find(i_params['id'])
|
95
|
+
r.assign_attributes(i_params)
|
96
|
+
r
|
97
|
+
else
|
98
|
+
relation.klass.new(i_params)
|
99
|
+
end
|
83
100
|
end
|
84
101
|
else
|
85
102
|
permitted_params[attributes_key] = if model_params[attributes_key].is_a?(Array)
|
86
|
-
model_params[attributes_key].map { |i|
|
103
|
+
models = relation.klass.find(model_params[attributes_key].map { |i| i['id'] }.compact)
|
104
|
+
model_params[attributes_key].map { |i|
|
105
|
+
i_params = filter_model_params(i, relation.klass.base_class, allow_id: true)
|
106
|
+
if i_params['id']
|
107
|
+
r = models.find { |r| r.id == i_params['id'] }
|
108
|
+
r.assign_attributes(i_params)
|
109
|
+
r
|
110
|
+
else
|
111
|
+
relation.klass.new(i_params)
|
112
|
+
end
|
113
|
+
}
|
87
114
|
else
|
88
|
-
filter_model_params(model_params[attributes_key], relation.klass.base_class, allow_id: true)
|
115
|
+
i_params = filter_model_params(model_params[attributes_key], relation.klass.base_class, allow_id: true)
|
116
|
+
if i_params['id']
|
117
|
+
r = relation.klass.find(i_params['id'])
|
118
|
+
r.assign_attributes(i_params)
|
119
|
+
r
|
120
|
+
else
|
121
|
+
relation.klass.new(i_params)
|
122
|
+
end
|
89
123
|
end
|
90
124
|
end
|
91
125
|
elsif relation.collection? && model_params.has_key?("#{relation.name.to_s.singularize}_ids")
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module StandardAPI
|
2
2
|
module Controller
|
3
3
|
|
4
|
-
delegate :preloadables, to: :helpers
|
4
|
+
delegate :preloadables, :model_partial, to: :helpers
|
5
5
|
|
6
6
|
def self.included(klass)
|
7
7
|
klass.helper_method :includes, :orders, :model, :models, :resource_limit,
|
8
8
|
:default_limit
|
9
9
|
klass.before_action :set_standardapi_headers
|
10
|
+
klass.before_action :includes, except: [:destroy, :add_resource, :remove_resource]
|
10
11
|
klass.rescue_from StandardAPI::ParameterMissing, with: :bad_request
|
11
12
|
klass.rescue_from StandardAPI::UnpermittedParameters, with: :bad_request
|
12
13
|
klass.append_view_path(File.join(File.dirname(__FILE__), 'views'))
|
@@ -73,7 +74,7 @@ module StandardAPI
|
|
73
74
|
end
|
74
75
|
else
|
75
76
|
if request.format == :html
|
76
|
-
render :
|
77
|
+
render :new, status: :bad_request
|
77
78
|
else
|
78
79
|
render :show, status: :bad_request
|
79
80
|
end
|
@@ -96,53 +97,97 @@ module StandardAPI
|
|
96
97
|
render :show, status: :ok
|
97
98
|
end
|
98
99
|
else
|
99
|
-
|
100
|
+
if request.format == :html
|
101
|
+
render :edit, status: :bad_request
|
102
|
+
else
|
103
|
+
render :show, status: :bad_request
|
104
|
+
end
|
100
105
|
end
|
101
106
|
end
|
102
107
|
|
103
108
|
def destroy
|
104
|
-
resources.find(params[:id])
|
109
|
+
records = resources.find(params[:id].split(','))
|
110
|
+
model.transaction { records.each(&:destroy!) }
|
111
|
+
|
105
112
|
head :no_content
|
106
113
|
end
|
107
114
|
|
108
115
|
def remove_resource
|
109
116
|
resource = resources.find(params[:id])
|
110
117
|
association = resource.association(params[:relationship])
|
111
|
-
subresource = association.klass.find_by_id(params[:resource_id])
|
112
118
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
119
|
+
result = case association
|
120
|
+
when ActiveRecord::Associations::CollectionAssociation
|
121
|
+
association.delete(association.klass.find(params[:resource_id]))
|
122
|
+
when ActiveRecord::Associations::SingularAssociation
|
123
|
+
if resource.send(params[:relationship])&.id&.to_s == params[:resource_id]
|
124
|
+
resource.update(params[:relationship] => nil)
|
118
125
|
end
|
119
|
-
head :no_content
|
120
|
-
else
|
121
|
-
head :not_found
|
122
126
|
end
|
127
|
+
head result ? :no_content : :not_found
|
123
128
|
end
|
124
129
|
|
125
130
|
def add_resource
|
126
131
|
resource = resources.find(params[:id])
|
127
132
|
association = resource.association(params[:relationship])
|
128
|
-
subresource = association.klass.
|
133
|
+
subresource = association.klass.find(params[:resource_id])
|
129
134
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
135
|
+
result = case association
|
136
|
+
when ActiveRecord::Associations::CollectionAssociation
|
137
|
+
association.concat(subresource)
|
138
|
+
when ActiveRecord::Associations::SingularAssociation
|
139
|
+
resource.update(params[:relationship] => subresource)
|
140
|
+
end
|
141
|
+
head result ? :created : :bad_request
|
142
|
+
rescue ActiveRecord::RecordNotUnique
|
143
|
+
render json: {errors: [
|
144
|
+
"Relationship between #{resource.class.name} and #{subresource.class.name} violates unique constraints"
|
145
|
+
]}, status: :bad_request
|
146
|
+
end
|
147
|
+
|
148
|
+
def create_resource
|
149
|
+
resource = resources.find(params[:id])
|
150
|
+
association = resource.association(params[:relationship])
|
151
|
+
|
152
|
+
subresource_params = if self.respond_to?("filter_#{model_name(association.klass)}_params", true)
|
153
|
+
self.send("filter_#{model_name(association.klass)}_params", params[model_name(association.klass)], id: params[:id])
|
154
|
+
elsif self.respond_to?("#{association.klass.model_name.singular}_params", true)
|
155
|
+
params.require(association.klass.model_name.singular).permit(self.send("#{association.klass.model_name.singular}_params"))
|
156
|
+
elsif self.respond_to?("filter_model_params", true)
|
157
|
+
filter_model_params(params[model_name(association.klass)], association.klass.base_class)
|
137
158
|
else
|
138
|
-
|
159
|
+
ActionController::Parameters.new
|
160
|
+
end
|
161
|
+
|
162
|
+
subresource = association.klass.new(subresource_params)
|
163
|
+
|
164
|
+
result = case association
|
165
|
+
when ActiveRecord::Associations::CollectionAssociation
|
166
|
+
association.concat(subresource)
|
167
|
+
when ActiveRecord::Associations::SingularAssociation
|
168
|
+
resource.update(params[:relationship] => subresource)
|
139
169
|
end
|
140
170
|
|
171
|
+
partial = model_partial(subresource)
|
172
|
+
partial_record_name = partial.split('/').last.to_sym
|
173
|
+
if result
|
174
|
+
render partial: partial, locals: {partial_record_name => subresource}, status: :created
|
175
|
+
else
|
176
|
+
render partial: partial, locals: {partial_record_name => subresource}, status: :bad_request
|
177
|
+
end
|
141
178
|
end
|
142
179
|
|
180
|
+
def mask
|
181
|
+
@mask ||= Hash.new do |hash, key|
|
182
|
+
hash[key] = mask_for(key)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
143
186
|
# Override if you want to support masking
|
144
|
-
def
|
145
|
-
|
187
|
+
def mask_for(table_name)
|
188
|
+
# case table_name
|
189
|
+
# when 'accounts'
|
190
|
+
# end
|
146
191
|
end
|
147
192
|
|
148
193
|
module ClassMethods
|
@@ -165,7 +210,11 @@ module StandardAPI
|
|
165
210
|
end
|
166
211
|
|
167
212
|
def model
|
168
|
-
|
213
|
+
if action_name&.end_with?('_resource')
|
214
|
+
self.class.model.reflect_on_association(params[:relationship]).klass
|
215
|
+
else
|
216
|
+
self.class.model
|
217
|
+
end
|
169
218
|
end
|
170
219
|
|
171
220
|
def models
|
@@ -215,8 +264,7 @@ module StandardAPI
|
|
215
264
|
end
|
216
265
|
|
217
266
|
def resources
|
218
|
-
|
219
|
-
query = model.filter(params['where']).filter(mask)
|
267
|
+
query = self.class.model.filter(params['where']).filter(mask[self.class.model.table_name.to_sym])
|
220
268
|
|
221
269
|
if params[:distinct_on]
|
222
270
|
query = query.distinct_on(params[:distinct_on])
|
@@ -234,9 +282,29 @@ module StandardAPI
|
|
234
282
|
|
235
283
|
query
|
236
284
|
end
|
285
|
+
|
286
|
+
def nested_includes(model, attributes)
|
287
|
+
includes = {}
|
288
|
+
attributes&.each do |key, value|
|
289
|
+
if association = model.reflect_on_association(key)
|
290
|
+
includes[key] = nested_includes(association.klass, value)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
includes
|
294
|
+
end
|
237
295
|
|
238
296
|
def includes
|
239
|
-
@includes ||=
|
297
|
+
@includes ||= if params[:include]
|
298
|
+
StandardAPI::Includes.sanitize(params[:include], model_includes)
|
299
|
+
else
|
300
|
+
{}
|
301
|
+
end
|
302
|
+
|
303
|
+
if (action_name == 'create' || action_name == 'update') && model && params.has_key?(model.model_name.singular)
|
304
|
+
@includes.reverse_merge!(nested_includes(model, params[model.model_name.singular].to_unsafe_h))
|
305
|
+
end
|
306
|
+
|
307
|
+
@includes
|
240
308
|
end
|
241
309
|
|
242
310
|
def required_orders
|
data/lib/standard_api/helpers.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
module StandardAPI
|
2
2
|
module Helpers
|
3
3
|
|
4
|
+
def serialize_attribute(json, record, name, type)
|
5
|
+
value = record.send(name)
|
6
|
+
|
7
|
+
json.set! name, type == :binary ? value&.unpack1('H*') : value
|
8
|
+
end
|
9
|
+
|
4
10
|
def preloadables(record, includes)
|
5
11
|
preloads = {}
|
6
12
|
|
@@ -11,31 +17,25 @@ module StandardAPI
|
|
11
17
|
preloads[key] = value
|
12
18
|
when Hash, ActiveSupport::HashWithIndifferentAccess
|
13
19
|
if !value.keys.any? { |x| ['when', 'where', 'limit', 'offset', 'order', 'distinct'].include?(x) }
|
14
|
-
|
15
|
-
preloads[key] = preloadables_hash(reflection.klass, value)
|
16
|
-
end
|
20
|
+
preloads[key.to_sym] = preloadables_hash(value)
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
22
|
-
preloads.
|
26
|
+
preloads.present? ? record.preload(preloads) : record
|
23
27
|
end
|
24
28
|
|
25
|
-
def preloadables_hash(
|
29
|
+
def preloadables_hash(iclds)
|
26
30
|
preloads = {}
|
27
31
|
|
28
32
|
iclds.each do |key, value|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
when
|
34
|
-
|
35
|
-
if !reflection.polymorphic?
|
36
|
-
preloads[key] = preloadables_hash(reflection.klass, value)
|
37
|
-
end
|
38
|
-
end
|
33
|
+
case value
|
34
|
+
when true
|
35
|
+
preloads[key] = value
|
36
|
+
when Hash, ActiveSupport::HashWithIndifferentAccess
|
37
|
+
if !value.keys.any? { |x| [ 'when', 'where', 'limit', 'offset', 'order', 'distinct' ].include?(x) }
|
38
|
+
preloads[key] = preloadables_hash(value)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -97,13 +97,13 @@ module StandardAPI
|
|
97
97
|
|
98
98
|
case association = record.class.reflect_on_association(relation)
|
99
99
|
when ActiveRecord::Reflection::HasManyReflection, ActiveRecord::Reflection::HasAndBelongsToManyReflection, ActiveRecord::Reflection::HasOneReflection, ActiveRecord::Reflection::ThroughReflection
|
100
|
-
"#{record.model_name.cache_key}/#{record.id}/#{includes_to_cache_key(relation, subincludes)}-#{timestamp.utc.
|
100
|
+
"#{record.model_name.cache_key}/#{record.id}/#{includes_to_cache_key(relation, subincludes)}-#{timestamp.utc.to_fs(record.cache_timestamp_format)}"
|
101
101
|
when ActiveRecord::Reflection::BelongsToReflection
|
102
102
|
klass = association.options[:polymorphic] ? record.send(association.foreign_type).constantize : association.klass
|
103
103
|
if subincludes.empty?
|
104
|
-
"#{klass.model_name.cache_key}/#{record.send(association.foreign_key)}-#{timestamp.utc.
|
104
|
+
"#{klass.model_name.cache_key}/#{record.send(association.foreign_key)}-#{timestamp.utc.to_fs(klass.cache_timestamp_format)}"
|
105
105
|
else
|
106
|
-
"#{klass.model_name.cache_key}/#{record.send(association.foreign_key)}/#{digest_hash(sort_hash(subincludes))}-#{timestamp.utc.
|
106
|
+
"#{klass.model_name.cache_key}/#{record.send(association.foreign_key)}/#{digest_hash(sort_hash(subincludes))}-#{timestamp.utc.to_fs(klass.cache_timestamp_format)}"
|
107
107
|
end
|
108
108
|
else
|
109
109
|
raise ArgumentError, 'Unkown association type'
|
@@ -156,7 +156,9 @@ module StandardAPI
|
|
156
156
|
|
157
157
|
def json_column_type(sql_type)
|
158
158
|
case sql_type
|
159
|
-
when '
|
159
|
+
when 'binary', 'bytea'
|
160
|
+
'binary'
|
161
|
+
when /timestamp(\(\d+\))? without time zone/
|
160
162
|
'datetime'
|
161
163
|
when 'time without time zone'
|
162
164
|
'datetime'
|
@@ -164,9 +166,7 @@ module StandardAPI
|
|
164
166
|
'string'
|
165
167
|
when 'json'
|
166
168
|
'hash'
|
167
|
-
when 'bigint'
|
168
|
-
'integer'
|
169
|
-
when 'integer'
|
169
|
+
when 'smallint', 'bigint', 'integer'
|
170
170
|
'integer'
|
171
171
|
when 'jsonb'
|
172
172
|
'hash'
|
data/lib/standard_api/railtie.rb
CHANGED
@@ -20,4 +20,21 @@ module StandardAPI
|
|
20
20
|
end
|
21
21
|
|
22
22
|
end
|
23
|
+
|
24
|
+
module AutosaveByDefault
|
25
|
+
def self.included base
|
26
|
+
base.class_eval do
|
27
|
+
class <<self
|
28
|
+
alias_method :standard_build, :build
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.build(model, name, scope, options, &block)
|
32
|
+
options[:autosave] = true
|
33
|
+
standard_build(model, name, scope, options, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
::ActiveRecord::Associations::Builder::Association.include(AutosaveByDefault)
|
23
40
|
end
|