apiql 0.4.8 → 1.0.5
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 +101 -40
- data/apiql.gemspec +1 -1
- data/assets/javascripts/apiql.js +36 -7
- data/lib/apiql.rb +170 -103
- metadata +6 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50c3aec2e1f759145b3e6bf73e6bb9e810793360bf9c08aaec98095a83fa42c4
|
4
|
+
data.tar.gz: 331b529d03c24fc189ab17f1b9d9d7144a53c21b22812434ed116a602b927c6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2249d7fb31711753c4db8dcd9ae711ba490bf587e99529f078e233b96de7f4827e3d01f920dc069382c172b2d98eed362a4f82b24a537d1776499445cbac0171
|
7
|
+
data.tar.gz: 332296fa978d1fa1e68dfe3e3e852f5e91cd3da8ed8dc344e368454572273a6a6988d4197d1454b1dbb51199f220158b623ef2f2049fb737740b3cb8d7fe88f0
|
data/README.md
CHANGED
@@ -6,37 +6,12 @@ It compiles requests into Hashes for faster rendering.
|
|
6
6
|
|
7
7
|
It automatically detects nested entities and eager-loads them for faster DB access!
|
8
8
|
|
9
|
-
Define your responder (requested methods):
|
10
|
-
|
11
|
-
```ruby
|
12
|
-
class UserAPIQL < ::APIQL
|
13
|
-
def me
|
14
|
-
authorize! :show, ::User
|
15
|
-
|
16
|
-
current_user
|
17
|
-
end
|
18
|
-
|
19
|
-
def authenticate(email, password)
|
20
|
-
user = ::User.find_by(email)
|
21
|
-
user.authenticate(password)
|
22
|
-
|
23
|
-
user
|
24
|
-
end
|
25
|
-
|
26
|
-
def logout
|
27
|
-
current_user&.logout
|
28
|
-
|
29
|
-
:ok
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
```
|
34
9
|
In controller or Grape API endpoint, handler of POST /apiql method (or any other, see below APIQL.endpoint):
|
35
10
|
|
36
11
|
```ruby
|
37
12
|
def apiql
|
38
13
|
schema = APIQL.cache(params)
|
39
|
-
|
14
|
+
APIQL.new(self, :session, :current_user, :params).render(schema)
|
40
15
|
end
|
41
16
|
|
42
17
|
```
|
@@ -46,12 +21,41 @@ Define presenters for your models:
|
|
46
21
|
|
47
22
|
```ruby
|
48
23
|
class User < ApplicationRecord
|
24
|
+
after_commit { APIQL.drop_cache(self) }
|
25
|
+
|
49
26
|
class Entity < ::APIQL::Entity
|
50
27
|
attributes :full_name, :email, :token, :role, :roles # any attributes, methods or associations
|
51
28
|
|
52
29
|
def token # if defined, method will be called instead of attribute
|
53
30
|
object.token if object == current_user # directly access to current_user from context
|
54
31
|
end
|
32
|
+
|
33
|
+
def roles
|
34
|
+
APIQL.cacheable([object, roles], :roles, expires_in: 3600) do
|
35
|
+
roles.pluck(:name).join(', ')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def me
|
41
|
+
authorize! :show, ::User
|
42
|
+
|
43
|
+
current_user
|
44
|
+
end
|
45
|
+
|
46
|
+
def authenticate(email, password)
|
47
|
+
user = ::User.find_by(email)
|
48
|
+
user.authenticate(password)
|
49
|
+
|
50
|
+
user
|
51
|
+
end
|
52
|
+
|
53
|
+
def logout
|
54
|
+
current_user&.logout
|
55
|
+
|
56
|
+
:ok
|
57
|
+
end
|
58
|
+
end
|
55
59
|
end
|
56
60
|
|
57
61
|
has_many :roles
|
@@ -59,6 +63,14 @@ class User < ApplicationRecord
|
|
59
63
|
...
|
60
64
|
end
|
61
65
|
|
66
|
+
class Role < ApplicationRecord
|
67
|
+
after_commit { APIQL.drop_cache(self) }
|
68
|
+
|
69
|
+
class Entity < ::APIQL::Entity
|
70
|
+
attributes :id, :name
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
62
74
|
```
|
63
75
|
# JS:
|
64
76
|
|
@@ -74,13 +86,13 @@ APIQL.endpoint = "/apiql"
|
|
74
86
|
|
75
87
|
authenticate(email, password) {
|
76
88
|
apiql(`
|
77
|
-
logout()
|
89
|
+
User.logout()
|
78
90
|
|
79
|
-
authenticate(email, password) {
|
91
|
+
User.authenticate(email, password) {
|
80
92
|
token
|
81
93
|
}
|
82
94
|
|
83
|
-
me {
|
95
|
+
User.me {
|
84
96
|
email full_name role token
|
85
97
|
|
86
98
|
roles {
|
@@ -98,7 +110,7 @@ authenticate(email, password) {
|
|
98
110
|
|
99
111
|
logout() {
|
100
112
|
apiql(`
|
101
|
-
logout
|
113
|
+
User.logout
|
102
114
|
`)
|
103
115
|
.then(response => {
|
104
116
|
})
|
@@ -110,11 +122,11 @@ you can call methods on entities:
|
|
110
122
|
|
111
123
|
```javascript
|
112
124
|
apiql(`
|
113
|
-
authenticate(email, password) {
|
125
|
+
User.authenticate(email, password) {
|
114
126
|
token
|
115
127
|
}
|
116
128
|
|
117
|
-
user: me.reload {
|
129
|
+
user: User.me.reload {
|
118
130
|
email full_name role token
|
119
131
|
|
120
132
|
roles(filter) {
|
@@ -157,20 +169,69 @@ class APIQL
|
|
157
169
|
end
|
158
170
|
```
|
159
171
|
|
160
|
-
|
172
|
+
CRUD methods available for all models:
|
161
173
|
|
162
|
-
```
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
174
|
+
```js
|
175
|
+
apiql(`
|
176
|
+
User.create(user)
|
177
|
+
`, {
|
178
|
+
user: {
|
179
|
+
email: this.email,
|
180
|
+
full_name: this.full_name
|
181
|
+
}
|
182
|
+
})
|
183
|
+
.then(response => {
|
184
|
+
...
|
185
|
+
})
|
186
|
+
|
187
|
+
apiql(`
|
188
|
+
User.find(id) {
|
189
|
+
id email full_name
|
190
|
+
}
|
191
|
+
`, {
|
192
|
+
id: this.id
|
193
|
+
})
|
194
|
+
.then(response => {
|
195
|
+
...
|
196
|
+
})
|
197
|
+
|
198
|
+
apiql(`
|
199
|
+
User.all {
|
200
|
+
id email full_name
|
201
|
+
}
|
202
|
+
`)
|
203
|
+
.then(response => {
|
204
|
+
...
|
205
|
+
})
|
206
|
+
|
207
|
+
apiql(`
|
208
|
+
User.update(id, user)
|
209
|
+
`, {
|
210
|
+
id: this.id,
|
211
|
+
user: {
|
212
|
+
email: this.email,
|
213
|
+
full_name: this.full_name
|
214
|
+
}
|
215
|
+
})
|
216
|
+
.then(response => {
|
217
|
+
...
|
218
|
+
})
|
219
|
+
|
220
|
+
apiql(`
|
221
|
+
User.destroy(id)
|
222
|
+
`, {
|
223
|
+
id: this.id
|
224
|
+
})
|
225
|
+
.then(response => {
|
226
|
+
...
|
227
|
+
})
|
167
228
|
```
|
168
229
|
|
169
230
|
or mount methods from external modules:
|
170
231
|
|
171
232
|
```ruby
|
172
|
-
class
|
173
|
-
mount ::Services::User # all
|
233
|
+
class APIQL < ::APIQL
|
234
|
+
mount ::Services::User # all methods could be called with "user" prefix like "user.logout()"
|
174
235
|
mount ::Services::Common, as: '' # all methods could be called without prefixes
|
175
236
|
mount ::Services::Employer, as: 'dashboard" # all methods could be called with specified prefix
|
176
237
|
end
|
data/apiql.gemspec
CHANGED
data/assets/javascripts/apiql.js
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
const APIQL = {
|
2
2
|
on_error: null,
|
3
3
|
endpoint: '',
|
4
|
+
CSRFToken: '',
|
5
|
+
http: null,
|
4
6
|
|
5
7
|
hash: function(s) {
|
6
|
-
|
8
|
+
var hash = 0, i, chr
|
7
9
|
if(s.length === 0) return hash
|
8
10
|
for(i = 0; i < s.length; i++) {
|
9
11
|
chr = s.charCodeAt(i)
|
@@ -13,6 +15,35 @@ const APIQL = {
|
|
13
15
|
return hash
|
14
16
|
},
|
15
17
|
|
18
|
+
post: function(endpoint, data) {
|
19
|
+
return new Promise(function(resolve, reject) {
|
20
|
+
var http = new XMLHttpRequest()
|
21
|
+
http.open("POST", endpoint, true)
|
22
|
+
if(APIQL.CSRFToken.length > 0) {
|
23
|
+
http.setRequestHeader("X-CSRF-Token", APIQL.CSRFToken)
|
24
|
+
}
|
25
|
+
http.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
|
26
|
+
http.onload = function() {
|
27
|
+
APIQL.http = http
|
28
|
+
|
29
|
+
if(http.status >= 200 && http.status < 300) {
|
30
|
+
resolve({
|
31
|
+
status: http.status,
|
32
|
+
body: JSON.parse(http.responseText),
|
33
|
+
http: http
|
34
|
+
})
|
35
|
+
} else {
|
36
|
+
reject({
|
37
|
+
status: http.status,
|
38
|
+
body: JSON.parse(http.responseText),
|
39
|
+
http: http
|
40
|
+
})
|
41
|
+
}
|
42
|
+
}
|
43
|
+
http.send(JSON.stringify(data))
|
44
|
+
})
|
45
|
+
},
|
46
|
+
|
16
47
|
call: function(schema, params, form) {
|
17
48
|
if(!params) params = {}
|
18
49
|
if(!form) form = null
|
@@ -35,11 +66,10 @@ const APIQL = {
|
|
35
66
|
params.apiql = APIQL.hash(schema)
|
36
67
|
}
|
37
68
|
|
38
|
-
|
69
|
+
APIQL.post(APIQL.endpoint, form || params)
|
39
70
|
.then(function(response) {
|
40
71
|
resolve(response.body)
|
41
|
-
})
|
42
|
-
.catch(function(response) {
|
72
|
+
}, function(response) {
|
43
73
|
if(response.status == 401 && APIQL.on_error) {
|
44
74
|
APIQL.on_error(response)
|
45
75
|
return
|
@@ -53,11 +83,10 @@ const APIQL = {
|
|
53
83
|
params.apiql_request = schema
|
54
84
|
}
|
55
85
|
|
56
|
-
|
86
|
+
APIQL.post(APIQL.endpoint, form || params)
|
57
87
|
.then(function(response) {
|
58
88
|
resolve(response.body)
|
59
|
-
})
|
60
|
-
.catch(function(response) {
|
89
|
+
}, function(response) {
|
61
90
|
if(APIQL.on_error) {
|
62
91
|
APIQL.on_error(response)
|
63
92
|
} else {
|
data/lib/apiql.rb
CHANGED
@@ -2,7 +2,7 @@ class APIQL
|
|
2
2
|
module Rails
|
3
3
|
class Engine < ::Rails::Engine
|
4
4
|
initializer 'apiql.assets' do |app|
|
5
|
-
app.config.assets.paths << root.join('assets', 'javascripts').to_s
|
5
|
+
app.config.assets.paths << root.join('assets', 'javascripts').to_s if app.config.respond_to? :assets
|
6
6
|
end
|
7
7
|
end
|
8
8
|
end
|
@@ -14,69 +14,6 @@ class APIQL
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
module CRUD
|
18
|
-
def model(klass)
|
19
|
-
define_method "#{klass.name.pluralize.underscore}" do |page = nil, page_size = 10|
|
20
|
-
authorize! :read, klass
|
21
|
-
|
22
|
-
if page.present?
|
23
|
-
{
|
24
|
-
total: klass.count,
|
25
|
-
items: klass.eager_load(eager_load).offset(page * page_size).limit(page_size)
|
26
|
-
}
|
27
|
-
else
|
28
|
-
klass.eager_load(eager_load).all
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
define_method "#{klass.name.underscore}" do |id|
|
33
|
-
item = klass.eager_load(eager_load).find(id)
|
34
|
-
|
35
|
-
authorize! :read, item
|
36
|
-
|
37
|
-
item
|
38
|
-
end
|
39
|
-
|
40
|
-
define_method "#{klass.name.underscore}.create" do |params|
|
41
|
-
authorize! :create, klass
|
42
|
-
|
43
|
-
klass_entity = "#{klass.name}::Entity".constantize
|
44
|
-
|
45
|
-
if klass_entity.respond_to?(:create_params, params)
|
46
|
-
params = klass_entity.send(:create_params, params)
|
47
|
-
elsif klass_entity.respond_to?(:params, params)
|
48
|
-
params = klass_entity.send(:params, params)
|
49
|
-
end
|
50
|
-
|
51
|
-
klass.create!(params)
|
52
|
-
end
|
53
|
-
|
54
|
-
define_method "#{klass.name.underscore}.update" do |id, params|
|
55
|
-
item = klass.find(id)
|
56
|
-
|
57
|
-
authorize! :update, item
|
58
|
-
|
59
|
-
klass_entity = "#{klass.name}::Entity".constantize
|
60
|
-
|
61
|
-
if klass_entity.respond_to?(:update_params, params)
|
62
|
-
params = klass_entity.send(:update_params, params)
|
63
|
-
elsif klass_entity.respond_to?(:params, params)
|
64
|
-
params = klass_entity.send(:params, params)
|
65
|
-
end
|
66
|
-
|
67
|
-
item.update!(params)
|
68
|
-
end
|
69
|
-
|
70
|
-
define_method "#{klass.name.underscore}.destroy" do |id|
|
71
|
-
item = klass.find(id)
|
72
|
-
|
73
|
-
authorize! :destroy, item
|
74
|
-
|
75
|
-
item.destroy!
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
17
|
class Error < StandardError; end
|
81
18
|
class CacheMissed < StandardError; end
|
82
19
|
|
@@ -84,8 +21,6 @@ class APIQL
|
|
84
21
|
delegate :authorize!, to: :@context
|
85
22
|
|
86
23
|
class << self
|
87
|
-
include ::APIQL::CRUD
|
88
|
-
|
89
24
|
def mount(klass, as: nil)
|
90
25
|
as ||= klass.name.split('::').last.underscore
|
91
26
|
as += '.' if as.present?
|
@@ -106,18 +41,34 @@ class APIQL
|
|
106
41
|
|
107
42
|
if request.present?
|
108
43
|
request = compile(request)
|
109
|
-
|
44
|
+
::Rails.cache.write("api-ql-cache-#{request_id}", JSON.generate(request), expires_in: 31*24*60*60)
|
110
45
|
@@cache[request_id] = request
|
111
46
|
@@cache = {} if(@@cache.count > 1000)
|
112
47
|
else
|
113
48
|
request = @@cache[request_id]
|
114
|
-
request ||= JSON.parse(
|
49
|
+
request ||= JSON.parse(::Rails.cache.fetch("api-ql-cache-#{request_id}")) rescue nil
|
115
50
|
raise CacheMissed unless request.present? && request.is_a?(::Array)
|
116
51
|
end
|
117
52
|
|
118
53
|
request
|
119
54
|
end
|
120
55
|
|
56
|
+
def cacheable(dependencies, attr, expires_in: 31*24*60*60)
|
57
|
+
dependencies = dependencies.flatten.map { |obj| [obj.class.name, obj.id] }
|
58
|
+
name = ['api-ql-cache', dependencies, attr].flatten.join('-')
|
59
|
+
begin
|
60
|
+
raise if expires_in <= 0
|
61
|
+
JSON.parse(::Rails.cache.fetch(name))
|
62
|
+
rescue
|
63
|
+
(block_given? ? yield : nil).tap { |data| expires_in > 0 && ::Rails.cache.write(name, JSON.generate(data), expires_in: expires_in) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def drop_cache(obj)
|
68
|
+
return unless ::Rails.cache.respond_to? :delete_if
|
69
|
+
::Rails.cache.delete_if { |k, v| k =~ /api-ql.*-#{obj.class.name}-#{obj.id}-.*/ }
|
70
|
+
end
|
71
|
+
|
121
72
|
def simple_class?(value)
|
122
73
|
value.nil? ||
|
123
74
|
value.is_a?(TrueClass) || value.is_a?(FalseClass) ||
|
@@ -151,15 +102,6 @@ class APIQL
|
|
151
102
|
|
152
103
|
private
|
153
104
|
|
154
|
-
def redis
|
155
|
-
@redis ||=
|
156
|
-
begin
|
157
|
-
::Redis.new(host: 'localhost')
|
158
|
-
rescue
|
159
|
-
nil
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
105
|
def compile(schema)
|
164
106
|
result = []
|
165
107
|
|
@@ -201,7 +143,7 @@ class APIQL
|
|
201
143
|
|
202
144
|
ptr = pool.pop
|
203
145
|
elsif pool.any?
|
204
|
-
if reg = schema.match(/\A\s*((?<alias>[\w\.]+):\s*)?(?<name>[\w
|
146
|
+
if reg = schema.match(/\A\s*((?<alias>[\w\.]+):\s*)?(?<name>[\w\.\:\!\?]+)(\((?<params>.*?)\))?(?<rest>.*)\z/m)
|
205
147
|
schema = reg[:rest]
|
206
148
|
|
207
149
|
key = reg[:alias].present? ? "#{reg[:alias]}: #{reg[:name]}" : reg[:name]
|
@@ -213,7 +155,7 @@ class APIQL
|
|
213
155
|
else
|
214
156
|
raise Error, schema
|
215
157
|
end
|
216
|
-
elsif reg = schema.match(/\A\s*((?<alias>[\w\.]+):\s
|
158
|
+
elsif reg = schema.match(/\A\s*((?<alias>[\w\.]+):\s+)?(?<name>[\w\.\:\!\?]+)(\((?<params>((\w+)(\s*\,\s*\w+)*))?\))?\s*\{(?<rest>.*)\z/m)
|
217
159
|
schema = reg[:rest]
|
218
160
|
|
219
161
|
key = "#{reg[:alias] || reg[:name]}: #{reg[:name]}(#{reg[:params]})"
|
@@ -221,7 +163,7 @@ class APIQL
|
|
221
163
|
pool.push(ptr)
|
222
164
|
|
223
165
|
ptr = push_key.call(key, true)
|
224
|
-
elsif reg = schema.match(/\A\s*((?<alias>[\w\.]+):\s
|
166
|
+
elsif reg = schema.match(/\A\s*((?<alias>[\w\.]+):\s+)?(?<name>[\w\.\:\!\?]+)(\((?<params>((\w+)(\s*\,\s*\w+)*))?\))?\s*\n?(?<rest>.*)\z/m)
|
225
167
|
schema = reg[:rest]
|
226
168
|
|
227
169
|
key = "#{reg[:alias] || reg[:name]}: #{reg[:name]}(#{reg[:params]})"
|
@@ -255,7 +197,7 @@ class APIQL
|
|
255
197
|
schema.each do |call|
|
256
198
|
if call.is_a? ::Hash
|
257
199
|
call.each do |function, sub_schema|
|
258
|
-
reg = function.match(/\A((?<alias>[\w
|
200
|
+
reg = function.match(/\A((?<alias>[\w\.\:\!\?]+):\s+)?(?<name>[\w\.\:\!\?]+)(\((?<params>.*?)\))?\z/)
|
259
201
|
raise Error, function unless reg.present?
|
260
202
|
|
261
203
|
name = reg[:alias] || reg[:name]
|
@@ -263,9 +205,11 @@ class APIQL
|
|
263
205
|
params = @context.parse_params(reg[:params].presence)
|
264
206
|
|
265
207
|
@eager_load = APIQL::eager_loads(sub_schema)
|
266
|
-
|
208
|
+
|
209
|
+
data = call_function(function, *params)
|
210
|
+
|
267
211
|
if @eager_load.present? && !data.is_a?(::Hash) && !data.is_a?(::Array)
|
268
|
-
if data.respond_to?(:
|
212
|
+
if data.respond_to?(:includes)
|
269
213
|
data = data.includes(eager_load)
|
270
214
|
elsif data.respond_to?(:id)
|
271
215
|
data = data.class.includes(eager_load).find(data.id)
|
@@ -281,7 +225,7 @@ class APIQL
|
|
281
225
|
end
|
282
226
|
end
|
283
227
|
else
|
284
|
-
reg = call.match(/\A((?<alias>[\w
|
228
|
+
reg = call.match(/\A((?<alias>[\w\.\:\!\?]+):\s+)?(?<name>[\w\.\:\!\?]+)(\((?<params>.*?)\))?\z/)
|
285
229
|
raise Error, call unless reg.present?
|
286
230
|
|
287
231
|
name = reg[:alias] || reg[:name]
|
@@ -289,7 +233,9 @@ class APIQL
|
|
289
233
|
params = @context.parse_params(reg[:params].presence)
|
290
234
|
|
291
235
|
@eager_load = []
|
292
|
-
|
236
|
+
|
237
|
+
data = call_function(function, *params)
|
238
|
+
|
293
239
|
if data.is_a? Array
|
294
240
|
if data.any? { |item| !APIQL::simple_class?(item) }
|
295
241
|
data = nil
|
@@ -313,11 +259,112 @@ class APIQL
|
|
313
259
|
|
314
260
|
private
|
315
261
|
|
262
|
+
def call_function(name, *params)
|
263
|
+
if respond_to?(name) && (methods - Object.methods).include?(name.to_sym)
|
264
|
+
public_send(name, *params)
|
265
|
+
else
|
266
|
+
o = nil
|
267
|
+
|
268
|
+
names = name.split('.')
|
269
|
+
names.each_with_index do |name, index|
|
270
|
+
if o.nil?
|
271
|
+
o = "#{name}::Entity".constantize
|
272
|
+
o.instance_variable_set('@context', @context)
|
273
|
+
o.instance_variable_set('@eager_load', @eager_load)
|
274
|
+
@context.inject_delegators(self)
|
275
|
+
elsif o.superclass == ::APIQL::Entity || o.superclass.superclass == ::APIQL::Entity
|
276
|
+
if o.respond_to?(name) && (o.methods - Object.methods).include?(name.to_sym)
|
277
|
+
o =
|
278
|
+
if index == names.count - 1
|
279
|
+
o.public_send(name, *params)
|
280
|
+
else
|
281
|
+
o.public_send(name)
|
282
|
+
end
|
283
|
+
else
|
284
|
+
o = nil
|
285
|
+
break
|
286
|
+
end
|
287
|
+
else
|
288
|
+
objects = o
|
289
|
+
o = "#{o.name}::Entity".constantize
|
290
|
+
if o.respond_to?(name) && (o.methods - Object.methods).include?(name.to_sym)
|
291
|
+
o.instance_variable_set("@objects", objects)
|
292
|
+
o.instance_variable_set('@context', @context)
|
293
|
+
o.instance_variable_set('@eager_load', @eager_load)
|
294
|
+
@context.inject_delegators(self)
|
295
|
+
o =
|
296
|
+
if index == names.count - 1
|
297
|
+
o.public_send(name, *params)
|
298
|
+
else
|
299
|
+
o.public_send(name)
|
300
|
+
end
|
301
|
+
else
|
302
|
+
o = nil
|
303
|
+
break
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
o
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
316
312
|
class Entity
|
317
313
|
delegate :authorize!, to: :@context
|
318
314
|
|
319
315
|
class << self
|
320
|
-
|
316
|
+
def all
|
317
|
+
authorize! :read, @apiql_entity_class
|
318
|
+
|
319
|
+
@apiql_entity_class.eager_load(eager_load).all
|
320
|
+
end
|
321
|
+
|
322
|
+
def find(id)
|
323
|
+
item = @apiql_entity_class.eager_load(eager_load).find(id)
|
324
|
+
|
325
|
+
authorize! :read, item
|
326
|
+
|
327
|
+
item
|
328
|
+
end
|
329
|
+
|
330
|
+
def create(params)
|
331
|
+
authorize! :create, @apiql_entity_class
|
332
|
+
|
333
|
+
if respond_to?(:create_params, params)
|
334
|
+
params = create_params(params)
|
335
|
+
elsif respond_to?(:params, params)
|
336
|
+
params = self.params(params)
|
337
|
+
end
|
338
|
+
|
339
|
+
@apiql_entity_class.create!(params)
|
340
|
+
end
|
341
|
+
|
342
|
+
def update(id, params)
|
343
|
+
item = @apiql_entity_class.find(id)
|
344
|
+
|
345
|
+
authorize! :update, item
|
346
|
+
|
347
|
+
if respond_to?(:update_params, params)
|
348
|
+
params = update_params(params)
|
349
|
+
elsif respond_to?(:params, params)
|
350
|
+
params = self.params(params)
|
351
|
+
end
|
352
|
+
|
353
|
+
item.update!(params)
|
354
|
+
end
|
355
|
+
|
356
|
+
def destroy(id)
|
357
|
+
item = @apiql_entity_class.find(id)
|
358
|
+
|
359
|
+
authorize! :destroy, item
|
360
|
+
|
361
|
+
item.destroy!
|
362
|
+
end
|
363
|
+
|
364
|
+
private
|
365
|
+
|
366
|
+
attr_reader :apiql_attributes, :objects
|
367
|
+
delegate :authorize!, to: :@context, private: true
|
321
368
|
|
322
369
|
def inherited(child)
|
323
370
|
super
|
@@ -328,6 +375,10 @@ class APIQL
|
|
328
375
|
|
329
376
|
child.instance_eval do
|
330
377
|
@apiql_attributes = attributes
|
378
|
+
|
379
|
+
names = child.name.split('::')
|
380
|
+
return unless names.last == 'Entity'
|
381
|
+
@apiql_entity_class = names[0..-2].join('::').constantize
|
331
382
|
end
|
332
383
|
end
|
333
384
|
|
@@ -335,6 +386,14 @@ class APIQL
|
|
335
386
|
@apiql_attributes ||= []
|
336
387
|
@apiql_attributes += attrs.map(&:to_sym)
|
337
388
|
end
|
389
|
+
|
390
|
+
def eager_load
|
391
|
+
result = @eager_load
|
392
|
+
|
393
|
+
@eager_load = []
|
394
|
+
|
395
|
+
result
|
396
|
+
end
|
338
397
|
end
|
339
398
|
|
340
399
|
attr_reader :object
|
@@ -354,7 +413,7 @@ class APIQL
|
|
354
413
|
schema.each do |field|
|
355
414
|
if field.is_a? Hash
|
356
415
|
field.each do |field, sub_schema|
|
357
|
-
reg = field.match(/\A((?<alias>[\w
|
416
|
+
reg = field.match(/\A((?<alias>[\w\.\!\?]+):\s*)?(?<name>[\w\.\!\?]+)(\((?<params>.*?)\))?\z/)
|
358
417
|
raise Error, field unless reg.present?
|
359
418
|
|
360
419
|
name = reg[:alias] || reg[:name]
|
@@ -368,7 +427,7 @@ class APIQL
|
|
368
427
|
end
|
369
428
|
end
|
370
429
|
else
|
371
|
-
reg = field.match(/\A((?<alias>[\w
|
430
|
+
reg = field.match(/\A((?<alias>[\w\.\!\?]+):\s*)?(?<name>[\w\.\!\?]+)(\((?<params>.*?)\))?\z/)
|
372
431
|
raise Error, field unless reg.present?
|
373
432
|
|
374
433
|
name = reg[:alias] || reg[:name]
|
@@ -396,20 +455,31 @@ class APIQL
|
|
396
455
|
names = field.split('.')
|
397
456
|
if names.count > 1
|
398
457
|
o = nil
|
399
|
-
names.
|
458
|
+
names.each_with_index do |field, index|
|
400
459
|
if o.nil?
|
401
460
|
return unless field.to_sym.in? self.class.apiql_attributes
|
402
461
|
|
403
|
-
if respond_to?
|
404
|
-
o = public_send(field
|
462
|
+
if respond_to?(field) && (methods - Object.methods).include?(name.to_sym)
|
463
|
+
o = public_send(field)
|
405
464
|
else
|
406
|
-
o = object.public_send(field
|
465
|
+
o = object.public_send(field)
|
407
466
|
end
|
408
467
|
|
409
468
|
break if o.nil?
|
410
469
|
else
|
411
|
-
|
412
|
-
|
470
|
+
objects = o
|
471
|
+
o = "#{o.name}::Entity".constantize
|
472
|
+
o.instance_variable_set("@objects", objects)
|
473
|
+
o.instance_variable_set('@context', @context)
|
474
|
+
o.instance_variable_set('@eager_load', @eager_load)
|
475
|
+
@context.inject_delegators(self)
|
476
|
+
|
477
|
+
if o.respond_to?(field) && (o.methods - Object.methods).include?(name.to_sym)
|
478
|
+
if index == names.count - 1
|
479
|
+
o = o.public_send(field, *params)
|
480
|
+
else
|
481
|
+
o = o.public_send(field)
|
482
|
+
end
|
413
483
|
else
|
414
484
|
o = nil
|
415
485
|
break
|
@@ -419,9 +489,9 @@ class APIQL
|
|
419
489
|
|
420
490
|
o
|
421
491
|
else
|
422
|
-
return unless field.to_sym.in? self.class.apiql_attributes
|
492
|
+
return unless field.to_sym.in? self.class.send(:apiql_attributes)
|
423
493
|
|
424
|
-
if respond_to?
|
494
|
+
if respond_to?(field) && (methods - Object.methods).include?(name.to_sym)
|
425
495
|
public_send(field, *params)
|
426
496
|
else
|
427
497
|
object.public_send(field, *params)
|
@@ -473,10 +543,10 @@ class APIQL
|
|
473
543
|
|
474
544
|
field.split('.').each do |name|
|
475
545
|
if o.nil?
|
476
|
-
o = object[name.to_sym]
|
546
|
+
o = object[name.to_sym] || object[name.to_s]
|
477
547
|
break if o.nil?
|
478
548
|
else
|
479
|
-
if o.respond_to? name
|
549
|
+
if o.respond_to?(name) && (o.methods - Object.methods).include?(name.to_sym)
|
480
550
|
o = o.public_send(name)
|
481
551
|
else
|
482
552
|
o = nil
|
@@ -504,6 +574,7 @@ class APIQL
|
|
504
574
|
|
505
575
|
def inject_delegators(target)
|
506
576
|
@fields.each do |field|
|
577
|
+
next if target.respond_to?(field)
|
507
578
|
target.class_eval do
|
508
579
|
delegate field, to: :@context
|
509
580
|
end
|
@@ -537,11 +608,7 @@ class APIQL
|
|
537
608
|
begin
|
538
609
|
"#{value.class.name}::Entity".constantize.new(value, self).render(schema)
|
539
610
|
rescue StandardError
|
540
|
-
|
541
|
-
raise
|
542
|
-
else
|
543
|
-
nil
|
544
|
-
end
|
611
|
+
raise
|
545
612
|
end
|
546
613
|
end
|
547
614
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apiql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitry Silchenko
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -69,7 +69,7 @@ homepage: https://github.com/desofto/apiql
|
|
69
69
|
licenses:
|
70
70
|
- MIT
|
71
71
|
metadata: {}
|
72
|
-
post_install_message:
|
72
|
+
post_install_message:
|
73
73
|
rdoc_options: []
|
74
74
|
require_paths:
|
75
75
|
- lib
|
@@ -84,9 +84,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
84
|
- !ruby/object:Gem::Version
|
85
85
|
version: '0'
|
86
86
|
requirements: []
|
87
|
-
|
88
|
-
|
89
|
-
signing_key:
|
87
|
+
rubygems_version: 3.2.15
|
88
|
+
signing_key:
|
90
89
|
specification_version: 4
|
91
90
|
summary: Implementation of the API language similar to GraphQL for Ruby on Rails
|
92
91
|
test_files: []
|