standardapi 6.1.0 → 7.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|