hyper-resource 1.0.0.lap86 → 1.0.0.lap87
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +206 -1
- data/lib/hyper_record/class_methods.rb +53 -1
- data/lib/hyper_record/server_class_methods.rb +21 -0
- data/lib/hyperloop/resource/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d276e459e66303c270a3fd19ad817000f7591604673852f2084c1b591b1bf4a
|
4
|
+
data.tar.gz: 14cfa291e0a603badf2f8e4cb9484e3996e5708a2cc059ad1dc5b8ef111784f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4d5e94abd9001519c6455257887eec88937c9dadfa1f41527fc5e1516e1d18432e9c6ea87497d041ed2d599985a80d47ae6fc9b4b20ced7d5613c75fb7aebd8
|
7
|
+
data.tar.gz: 72a52d2bc6dce43d4c1c2d2c6827494629786782b58dcb5e9ee7d6bffb822a83830bbd957a15ed9e2d4a56fad9459e69b63f9b107f77822a98a18aaabe40c8af
|
data/README.md
CHANGED
@@ -112,4 +112,209 @@ EXAMPLE
|
|
112
112
|
|
113
113
|
## Implementation
|
114
114
|
|
115
|
-
|
115
|
+
## Implementation
|
116
|
+
|
117
|
+
Hyperloop needs to be installed and working before you install HyperResource. These instructions are likely to change/be simplified as this Gem matures.
|
118
|
+
|
119
|
+
+ Add the gems (make sure its the latest version)
|
120
|
+
|
121
|
+
`gem 'hyper-resource', '1.0.0.lap86'`
|
122
|
+
`gem 'opal-jquery', github: 'janbiedermann/opal-jquery', branch: 'why_to_n'`
|
123
|
+
`gem 'opal-activesupport', github: 'opal/opal-activesupport', branch: 'master'`
|
124
|
+
|
125
|
+
+ Require HyperResource in your `hyperloop_webpack_loader.rb` file
|
126
|
+
|
127
|
+
`require 'hyper-resource'`
|
128
|
+
`require 'opal-jquery'`
|
129
|
+
|
130
|
+
+ Update your `application_record.rb` file and move it to the `hyperloop/models` folder
|
131
|
+
|
132
|
+
```
|
133
|
+
# application_record.rb
|
134
|
+
if RUBY_ENGINE == 'opal'
|
135
|
+
class ApplicationRecord
|
136
|
+
def self.inherited(base)
|
137
|
+
base.include(HyperRecord)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
else
|
141
|
+
class ApplicationRecord < ActiveRecord::Base
|
142
|
+
# when updating this part, also update the ApplicationRecord in app/models/application_record.rb
|
143
|
+
# for rails eager loading in production, so production doesn't fail
|
144
|
+
self.abstract_class = true
|
145
|
+
extend HyperRecord::ServerClassMethods
|
146
|
+
include HyperRecord::PubSub
|
147
|
+
end
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
+ Move the models you want on the client to the `hyperloop/models` folder
|
152
|
+
|
153
|
+
+ Make sure you guard anything in your model which you do not want on the client:
|
154
|
+
|
155
|
+
```
|
156
|
+
unless RUBY_ENGINE == 'opal'
|
157
|
+
# herein stuff that you do not want on the client (Devise, etc)
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
+ Add Pusher to your gemfile
|
162
|
+
|
163
|
+
`gem pusher`
|
164
|
+
|
165
|
+
+ Add it with Yarn
|
166
|
+
|
167
|
+
`yarn add pusher-js`
|
168
|
+
|
169
|
+
+ Then import in your `app.js`
|
170
|
+
|
171
|
+
`import Pusher from 'pusher-js';`
|
172
|
+
`global.Pusher = Pusher;`
|
173
|
+
|
174
|
+
+ Add your api endpoint to `hyperloop.rb`
|
175
|
+
|
176
|
+
`config.resource_api_base_path = '/api'`
|
177
|
+
|
178
|
+
+ Create you API controllers as normal - ensure they return JSON in this format
|
179
|
+
|
180
|
+
```
|
181
|
+
{
|
182
|
+
"members":[
|
183
|
+
{"member":{"id":1,"email":"a@b.com","first_name":"John","last_name":"Smith"}},
|
184
|
+
{"member":{"id":2,"email":"b@c.com","first_name":null,"last_name":null}}
|
185
|
+
]
|
186
|
+
}
|
187
|
+
```
|
188
|
+
|
189
|
+
Rabl gem example view:
|
190
|
+
|
191
|
+
```
|
192
|
+
collection @members, root: :members
|
193
|
+
attributes :id,
|
194
|
+
:email,
|
195
|
+
:first_name,
|
196
|
+
:last_name
|
197
|
+
```
|
198
|
+
|
199
|
+
+ Create your API controller and make sure to implement `show` as this is called by HyperResource. Please see the example controller below for details on pub_sub
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
module Api
|
203
|
+
class PersonasController < ApplicationController
|
204
|
+
# GET /api/personas.json
|
205
|
+
def index
|
206
|
+
authorize(Persona)
|
207
|
+
|
208
|
+
@personas = Persona.all
|
209
|
+
subscribe_scope(@personas, Persona, :all)
|
210
|
+
respond_to do |format|
|
211
|
+
format.json {}
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# GET /api/personas/123.json
|
216
|
+
def show
|
217
|
+
@persona = Persona.find(params[:id])
|
218
|
+
|
219
|
+
authorize(@persona)
|
220
|
+
|
221
|
+
subscribe_record(@persona)
|
222
|
+
respond_to do |format|
|
223
|
+
format.json {}
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# POST /api/plans/1/personas.json
|
228
|
+
def create
|
229
|
+
authorize(Persona)
|
230
|
+
|
231
|
+
@persona = Persona.new(persona_params)
|
232
|
+
|
233
|
+
subscribe_record(@persona)
|
234
|
+
respond_to do |format|
|
235
|
+
if @persona.save
|
236
|
+
subscribe_record(@persona)
|
237
|
+
publish_scope(Persona, :all)
|
238
|
+
format.json { render :show, status: :created }
|
239
|
+
else
|
240
|
+
format.json { render json: @persona.errors, status: :unprocessable_entity }
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# PATCH/PUT /api/personas/1.json
|
246
|
+
def update
|
247
|
+
@persona = Persona.find(params[:id])
|
248
|
+
@persona.assign_attributes(persona_params)
|
249
|
+
|
250
|
+
authorize(@persona)
|
251
|
+
|
252
|
+
respond_to do |format|
|
253
|
+
if @persona.update(persona_params)
|
254
|
+
pub_sub_record(@persona)
|
255
|
+
format.json { render :show, status: :ok }
|
256
|
+
else
|
257
|
+
format.json { render json: @persona.errors, status: :unprocessable_entity }
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# DELETE /personas/1.json
|
263
|
+
def destroy
|
264
|
+
@persona = Persona.find(params[:id])
|
265
|
+
# authorize @persona
|
266
|
+
|
267
|
+
@persona.destroy
|
268
|
+
publish_record(@persona)
|
269
|
+
respond_to do |format|
|
270
|
+
format.json { head :no_content }
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
|
276
|
+
def persona_params
|
277
|
+
permitted_keys = Persona.attribute_names.map(&:to_sym)
|
278
|
+
%i[id created_at updated_at].each do |key|
|
279
|
+
permitted_keys.delete(key)
|
280
|
+
end
|
281
|
+
params.require(:data).permit(permitted_keys)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
```
|
286
|
+
|
287
|
+
+ Install Redis and add the following to your `hyperloop.rb`
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
config.redis_instance = if ENV['REDIS_URL']
|
291
|
+
Redis.new(url: ENV['REDIS_URL'])
|
292
|
+
else
|
293
|
+
Redis.new
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
+ Add the following to your `ApplicationController`
|
298
|
+
|
299
|
+
```
|
300
|
+
include Hyperloop::Resource::PubSub
|
301
|
+
```
|
302
|
+
|
303
|
+
+ Add these routes:
|
304
|
+
|
305
|
+
```
|
306
|
+
namespace :api, defaults: { format: :json } do
|
307
|
+
|
308
|
+
# introspection
|
309
|
+
# get '/:model_klass/relations', to: 'relations#index'
|
310
|
+
# get '/:model_klass/methods', to: 'methods#index'
|
311
|
+
# get '/:model_klass/methods/:id', to: 'methods#show'
|
312
|
+
# patch '/:model_klass/methods/:id', to: 'methods#update'
|
313
|
+
# get '/:model_klass/properties', to: 'properties#index'
|
314
|
+
get '/:model_klass/scopes', to: 'scopes#index'
|
315
|
+
get '/:model_klass/scopes/:id', to: 'scopes#show'
|
316
|
+
patch '/:model_klass/scopes/:id', to: 'scopes#update'
|
317
|
+
|
318
|
+
```
|
319
|
+
|
320
|
+
+ Add the `ScopesController` as per the example in this Gem
|
@@ -122,6 +122,58 @@ module HyperRecord
|
|
122
122
|
# end
|
123
123
|
end
|
124
124
|
|
125
|
+
# macro define collection_query_method, RPC on instance level of a record of current HyperRecord class
|
126
|
+
# The supplied block must return a Array of Records!
|
127
|
+
#
|
128
|
+
# @param name [Symbol] name of method
|
129
|
+
# @param options [Hash] with known keys:
|
130
|
+
# default_result: result to present during render during method call in progress, is a Array by default, should be a Enumerable in any case
|
131
|
+
#
|
132
|
+
# This macro defines additional methods:
|
133
|
+
def collection_query_method(name, options = { default_result: []})
|
134
|
+
# @!method promise_[name]
|
135
|
+
# @return [Promise] on success the .then block will receive the result of the RPC call as arg
|
136
|
+
# on failure the .fail block will receive the HTTP response object as arg
|
137
|
+
define_method("promise_#{name}") do
|
138
|
+
@fetch_states[name] = 'i'
|
139
|
+
unless @rest_methods.has_key?(name)
|
140
|
+
@rest_methods[name] = options
|
141
|
+
@rest_methods[name] = { result: options[:default_result] }
|
142
|
+
end
|
143
|
+
raise "#{self.class.to_s}[_no_id_].#{name}, can't execute instance collection_query_method without id!" unless self.id
|
144
|
+
self.class._promise_get_or_patch("#{resource_base_uri}/#{self.id}/methods/#{name}.json?timestamp=#{`Date.now() + Math.random()`}").then do |response_json|
|
145
|
+
collection = self.class._convert_array_to_collection(response_json[:result], self)
|
146
|
+
@rest_methods[name][:result] = collection
|
147
|
+
@fetch_states[name] = 'f'
|
148
|
+
_notify_observers
|
149
|
+
@rest_methods[name][:result]
|
150
|
+
end.fail do |response|
|
151
|
+
error_message = "#{self.class.to_s}[#{self.id}].#{name}, a collection_query_method, failed to execute!"
|
152
|
+
`console.error(error_message)`
|
153
|
+
response
|
154
|
+
end
|
155
|
+
end
|
156
|
+
# @!method [name]
|
157
|
+
# @return result either the default_result ass specified in the options or the real result if the RPC call already finished
|
158
|
+
define_method(name) do
|
159
|
+
_register_observer
|
160
|
+
unless @rest_methods.has_key?(name)
|
161
|
+
@rest_methods[name] = options
|
162
|
+
@rest_methods[name] = { result: options[:default_result] }
|
163
|
+
end
|
164
|
+
unless @fetch_states.has_key?(name) && 'fi'.include?(@fetch_states[name])
|
165
|
+
self.send("promise_#{name}")
|
166
|
+
end
|
167
|
+
@rest_methods[name][:result]
|
168
|
+
end
|
169
|
+
# @!method update_[name] mark internal structures so that the method is called again once it is requested again
|
170
|
+
# @return nil
|
171
|
+
define_method("update_#{name}") do
|
172
|
+
@fetch_states[name] = 'u'
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
125
177
|
# create a new instance of current HyperRecord class and save it to the db
|
126
178
|
#
|
127
179
|
# @param record_hash [Hash] optional data for the record
|
@@ -507,7 +559,7 @@ module HyperRecord
|
|
507
559
|
end
|
508
560
|
end
|
509
561
|
|
510
|
-
# macro define
|
562
|
+
# macro define rest_methods, RPC on instance level of a record of current HyperRecord class
|
511
563
|
#
|
512
564
|
# @param name [Symbol] name of method
|
513
565
|
# @param options [Hash] with known keys:
|
@@ -1,5 +1,26 @@
|
|
1
1
|
module HyperRecord
|
2
2
|
module ServerClassMethods
|
3
|
+
# DSL for defining collection_query_methods
|
4
|
+
# @param name [Symbol] name of the method
|
5
|
+
# @param options [Hash] known key: default_result, used client side
|
6
|
+
def collection_query_method(name, options = { default_result: [] }, &block)
|
7
|
+
rest_methods[name] = options
|
8
|
+
rest_methods[name][:params] = block.arity
|
9
|
+
define_method(name) do
|
10
|
+
array_of_records = instance_exec(&block)
|
11
|
+
array_of_records.map do |record|
|
12
|
+
subscribe_record(record)
|
13
|
+
record_json = record.as_json
|
14
|
+
record_model = record.class.to_s.underscore
|
15
|
+
if record_json.has_key?(record_model)
|
16
|
+
record_json
|
17
|
+
else
|
18
|
+
{ record_model => record_json }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
3
24
|
# DSL for defining rest_class_methods
|
4
25
|
# @param name [Symbol] name of the method
|
5
26
|
# @param options [Hash] known key: default_result, used client side
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hyper-resource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.lap87
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Biedermann
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: opal
|