sinja 1.2.3 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -4
- data/README.md +42 -0
- data/demo-app/Dockerfile +1 -1
- data/demo-app/Gemfile +3 -1
- data/demo-app/app.rb +3 -0
- data/demo-app/database.rb +2 -1
- data/lib/jsonapi/ember_serializer.rb +39 -0
- data/lib/sinja.rb +8 -8
- data/lib/sinja/helpers/nested.rb +4 -0
- data/lib/sinja/helpers/relationships.rb +9 -2
- data/lib/sinja/helpers/serializers.rb +6 -4
- data/lib/sinja/relationship_routes/has_many.rb +1 -1
- data/lib/sinja/relationship_routes/has_one.rb +1 -1
- data/lib/sinja/resource.rb +2 -2
- data/lib/sinja/resource_routes.rb +3 -3
- data/lib/sinja/version.rb +1 -1
- data/sinja.gemspec +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 851e8f43eabf59529257a1ef2fcc7a6df84f8ea5
|
4
|
+
data.tar.gz: b75968556128599080ba71d790d8b1671cbc89e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ddd2782e3e63efab5bfee81ace29615708e08cc9a137ba28451724543f98ee3f2f6d59eda0444a0859ebd9f4b8ccdc26ed661ebde0cbdf9b902e97e18f8d23f
|
7
|
+
data.tar.gz: 1fea65b16d22c9bbb22dc0af0ed4744308eaf5dfe217da0c054da4c57b40ae5d00a36617b06d114ae2a50b324a72ac45a4c17917bd61debed1ed812d35c5e0ef
|
data/.travis.yml
CHANGED
@@ -2,14 +2,18 @@ sudo: false
|
|
2
2
|
language: ruby
|
3
3
|
rvm:
|
4
4
|
- 2.3.3
|
5
|
+
- 2.4.0
|
5
6
|
- ruby-head
|
6
|
-
- jruby-9.1.
|
7
|
+
- jruby-9.1.7.0
|
7
8
|
- jruby-head
|
8
9
|
env:
|
9
10
|
- sinatra=1.4.7 rails=4.2.7.1
|
10
|
-
- sinatra=2.0.0.beta2 rails=5.0.
|
11
|
+
- sinatra=2.0.0.beta2 rails=5.0.1
|
11
12
|
jdk:
|
12
13
|
- oraclejdk8
|
13
14
|
before_install:
|
14
|
-
- gem
|
15
|
-
|
15
|
+
- gem install bundler
|
16
|
+
matrix:
|
17
|
+
allow_failures:
|
18
|
+
- rvm: ruby-head
|
19
|
+
- rvm: jruby-head
|
data/README.md
CHANGED
@@ -778,6 +778,24 @@ end
|
|
778
778
|
|
779
779
|
Please see the [demo-app](/demo-app) for a more complete example.
|
780
780
|
|
781
|
+
Finally, because the `role` helper is invoked several times and may return
|
782
|
+
different results throughout the request lifecycle, Sinja does not memoize
|
783
|
+
(cache the return value keyed by function signature) it. If you have an
|
784
|
+
expensive component of your role helper that is not context-dependent, it may
|
785
|
+
be worth memoizing yourself:
|
786
|
+
|
787
|
+
```ruby
|
788
|
+
helpers do
|
789
|
+
def role
|
790
|
+
@roles ||= expensive_role_lookup.freeze
|
791
|
+
|
792
|
+
@roles.dup.tap do |a|
|
793
|
+
a << :foo if bar
|
794
|
+
end
|
795
|
+
end
|
796
|
+
end
|
797
|
+
```
|
798
|
+
|
781
799
|
### Query Parameters
|
782
800
|
|
783
801
|
The {json:api} specification states that any unhandled query parameters should
|
@@ -1167,6 +1185,30 @@ The following matrix outlines which combinations of action helpers and
|
|
1167
1185
|
</tbody>
|
1168
1186
|
</table>
|
1169
1187
|
|
1188
|
+
#### Deferring Relationships
|
1189
|
+
|
1190
|
+
If you're side-loading multiple relationships, you may need one applied before
|
1191
|
+
another (e.g. set the author of a post before setting its tags). You can use
|
1192
|
+
the built-in `defer` helper to affect the order of operations:
|
1193
|
+
|
1194
|
+
```ruby
|
1195
|
+
has_one :author do
|
1196
|
+
graft do |rio|
|
1197
|
+
resource.author = Author.with_pk!(rio[:id].to_i)
|
1198
|
+
resource.save_changes
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
has_many :tags do
|
1203
|
+
replace do |rios|
|
1204
|
+
defer unless resource.author # come back to this if the author isn't set yet
|
1205
|
+
|
1206
|
+
tags = resource.author.preferred_tags
|
1207
|
+
# ..
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
```
|
1211
|
+
|
1170
1212
|
#### Avoiding Null Foreign Keys
|
1171
1213
|
|
1172
1214
|
Now, let's say our DBA is forward-thinking and wants to make the foreign key
|
data/demo-app/Dockerfile
CHANGED
data/demo-app/Gemfile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
+
gem 'jdbc-sqlite3', '~> 3.8', :platform=>:jruby
|
3
4
|
gem 'json', '~> 2.0'
|
5
|
+
gem 'sequel', '~> 4.43'
|
4
6
|
gem 'sinatra', '>= 2.0.0.beta2', '< 3'
|
5
7
|
gem 'sinatra-contrib', '>= 2.0.0.beta2', '< 3'
|
6
8
|
gem 'sinja',
|
7
9
|
git: 'https://github.com/mwpastore/sinja.git', branch: 'master'
|
8
10
|
gem 'sinja-sequel',
|
9
11
|
git: 'https://github.com/mwpastore/sinja-sequel.git', branch: 'master'
|
10
|
-
gem 'sqlite3', '~> 1.3'
|
12
|
+
gem 'sqlite3', '~> 1.3', :platform=>[:ruby, :mswin]
|
data/demo-app/app.rb
CHANGED
@@ -10,6 +10,9 @@ require_relative 'classes/comment'
|
|
10
10
|
require_relative 'classes/post'
|
11
11
|
require_relative 'classes/tag'
|
12
12
|
|
13
|
+
# Freeze database after creating tables and loading models
|
14
|
+
DB.freeze
|
15
|
+
|
13
16
|
configure :development do
|
14
17
|
set :server_settings, AccessLog: [] # avoid WEBrick double-logging issue
|
15
18
|
end
|
data/demo-app/database.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative 'boot'
|
|
5
5
|
DB = Sequel.connect ENV.fetch 'DATABASE_URL',
|
6
6
|
defined?(JRUBY_VERSION) ? 'jdbc:sqlite::memory:' : 'sqlite:/'
|
7
7
|
|
8
|
-
DB.extension
|
8
|
+
DB.extension(:freeze_datasets)
|
9
|
+
DB.extension(:pagination)
|
9
10
|
|
10
11
|
DB.loggers << Logger.new($stderr) if Sinatra::Base.development?
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'jsonapi-serializers'
|
3
|
+
|
4
|
+
module JSONAPI
|
5
|
+
module EmberSerializer
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
include Serializer
|
9
|
+
|
10
|
+
alias type_for_link type
|
11
|
+
alias format_name_for_link format_name
|
12
|
+
|
13
|
+
include InstanceMethods
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def type
|
19
|
+
object.class.name.demodulize.underscore.dasherize
|
20
|
+
end
|
21
|
+
|
22
|
+
def format_name(attribute_name)
|
23
|
+
attribute_name.to_s.underscore.camelize(:lower)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self_link
|
27
|
+
"#{base_url}/#{type_for_link}/#{id}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def relationship_self_link(attribute_name)
|
31
|
+
"#{self_link}/relationships/#{format_name_for_link(attribute_name)}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def relationship_related_link(attribute_name)
|
35
|
+
"#{self_link}/#{format_name_for_link(attribute_name)}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/sinja.rb
CHANGED
@@ -13,6 +13,7 @@ require 'sinja/resource'
|
|
13
13
|
require 'sinja/version'
|
14
14
|
|
15
15
|
module Sinja
|
16
|
+
DEFER_CODE = 357
|
16
17
|
MIME_TYPE = 'application/vnd.api+json'
|
17
18
|
ERROR_CODES = ObjectSpace.each_object(Class).to_a
|
18
19
|
.keep_if { |klass| klass < HttpError }
|
@@ -138,11 +139,14 @@ module Sinja
|
|
138
139
|
|
139
140
|
def can?(action)
|
140
141
|
roles = settings._resource_config[:resource].fetch(action, {})[:roles]
|
141
|
-
roles.nil? || roles.empty? || roles.intersect?(
|
142
|
+
roles.nil? || roles.empty? || roles.intersect?(role)
|
142
143
|
end
|
143
144
|
|
144
145
|
def content?
|
145
|
-
request.body.respond_to?(:size) && request.body.size > 0
|
146
|
+
request.body.respond_to?(:size) && request.body.size > 0 || begin
|
147
|
+
request.body.rewind
|
148
|
+
request.body.read(1)
|
149
|
+
end
|
146
150
|
end
|
147
151
|
|
148
152
|
def data
|
@@ -243,10 +247,6 @@ module Sinja
|
|
243
247
|
end
|
244
248
|
end
|
245
249
|
|
246
|
-
def memoized_role
|
247
|
-
@role ||= Roles[*role]
|
248
|
-
end
|
249
|
-
|
250
250
|
def sideloaded?
|
251
251
|
env.key?('sinja.passthru')
|
252
252
|
end
|
@@ -256,7 +256,7 @@ module Sinja
|
|
256
256
|
end
|
257
257
|
|
258
258
|
def role?(*roles)
|
259
|
-
Roles[*roles].intersect?(
|
259
|
+
Roles[*roles].intersect?(role)
|
260
260
|
end
|
261
261
|
|
262
262
|
def sanity_check!(resource_name, id=nil)
|
@@ -328,7 +328,7 @@ module Sinja
|
|
328
328
|
# trigger default procs
|
329
329
|
config = _sinja.resource_config[resource_name]
|
330
330
|
|
331
|
-
namespace
|
331
|
+
namespace %r{/#{resource_name}(?![^/])} do
|
332
332
|
define_singleton_method(:_resource_config) { config }
|
333
333
|
define_singleton_method(:resource_config) { config[:resource] }
|
334
334
|
|
data/lib/sinja/helpers/nested.rb
CHANGED
@@ -19,11 +19,18 @@ module Sinja
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def dispatch_relationship_requests!(id, methods: {}, **opts)
|
22
|
-
data.fetch(:relationships, {}).
|
23
|
-
|
22
|
+
rels = data.fetch(:relationships, {}).to_a
|
23
|
+
rels.each do |rel, body, rel_type=nil, count=0|
|
24
|
+
rel_type ||= settings._resource_config[:has_one].key?(rel) ? :has_one : :has_many
|
24
25
|
code, _, *json = dispatch_relationship_request id, rel,
|
25
26
|
opts.merge(:body=>body, :method=>methods.fetch(rel_type, :patch))
|
26
27
|
|
28
|
+
if code == DEFER_CODE && count == 0
|
29
|
+
rels << [rel, body, rel_type, count + 1]
|
30
|
+
|
31
|
+
next
|
32
|
+
end
|
33
|
+
|
27
34
|
# TODO: Gather responses and report all errors instead of only first?
|
28
35
|
# `halt' was called (instead of raise); rethrow it as best as possible
|
29
36
|
raise SideloadError.new(code, json) unless (200...300).cover?(code)
|
@@ -87,7 +87,7 @@ module Sinja
|
|
87
87
|
config.dig(:has_many, last_term.pluralize.to_sym, :fetch, :roles) ||
|
88
88
|
config.dig(:has_one, last_term.singularize.to_sym, :pluck, :roles)
|
89
89
|
|
90
|
-
throw :keep?, roles && (roles.empty? || roles.intersect?(
|
90
|
+
throw :keep?, roles && (roles.empty? || roles.intersect?(role))
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
@@ -170,12 +170,14 @@ module Sinja
|
|
170
170
|
def serialize_linkage(model, rel, options={})
|
171
171
|
options[:is_collection] = false
|
172
172
|
options[:skip_collection_check] = defined?(::Sequel::Model) && model.is_a?(::Sequel::Model)
|
173
|
-
options[:include] = rel.to_s
|
174
173
|
options = settings._sinja.serializer_opts.merge(options)
|
175
174
|
|
175
|
+
options[:serializer] ||= ::JSONAPI::Serializer.find_serializer_class(model, options)
|
176
|
+
options[:include] = options[:serializer].new(model, options).format_name(rel)
|
177
|
+
|
176
178
|
# TODO: This is extremely wasteful. Refactor JAS to expose the linkage serializer?
|
177
179
|
content = ::JSONAPI::Serializer.serialize(model, options)
|
178
|
-
content['data']['relationships'][
|
180
|
+
content['data']['relationships'].fetch(options[:include]).tap do |linkage|
|
179
181
|
%w[meta jsonapi].each do |key|
|
180
182
|
linkage[key] = content[key] if content.key?(key)
|
181
183
|
end
|
@@ -192,7 +194,7 @@ module Sinja
|
|
192
194
|
|
193
195
|
def error_hash(title: nil, detail: nil, source: nil)
|
194
196
|
[
|
195
|
-
{ id
|
197
|
+
{ :id=>SecureRandom.uuid }.tap do |hash|
|
196
198
|
hash[:title] = title if title
|
197
199
|
hash[:detail] = detail if detail
|
198
200
|
hash[:status] = status.to_s if status
|
data/lib/sinja/resource.rb
CHANGED
@@ -89,7 +89,7 @@ module Sinja
|
|
89
89
|
|
90
90
|
config = _resource_config[rel_type][rel] # trigger default proc
|
91
91
|
|
92
|
-
namespace %r{/[^/]+(?<r>/relationships)?/#{rel}} do
|
92
|
+
namespace %r{/[^/]+(?<r>/relationships)?/#{rel}(?![^/])} do
|
93
93
|
define_singleton_method(:resource_config) { config }
|
94
94
|
|
95
95
|
helpers Helpers::Nested do
|
@@ -97,7 +97,7 @@ module Sinja
|
|
97
97
|
parent = sideloaded? && env['sinja.passthru'].to_sym
|
98
98
|
|
99
99
|
roles, sideload_on = config.fetch(action, {}).values_at(:roles, :sideload_on)
|
100
|
-
roles.nil? || roles.empty? || roles.intersect?(
|
100
|
+
roles.nil? || roles.empty? || roles.intersect?(role) ||
|
101
101
|
parent && sideload_on.include?(parent) && super(parent, *args)
|
102
102
|
end
|
103
103
|
|
@@ -9,7 +9,7 @@ module Sinja
|
|
9
9
|
app.def_action_helper(app, :update, :roles)
|
10
10
|
app.def_action_helper(app, :destroy, :roles)
|
11
11
|
|
12
|
-
app.
|
12
|
+
app.options '', :qcaptures=>{ :filter=>:id } do
|
13
13
|
allow :get=>:show
|
14
14
|
end
|
15
15
|
|
@@ -38,7 +38,7 @@ module Sinja
|
|
38
38
|
serialize_models(resources, opts)
|
39
39
|
end
|
40
40
|
|
41
|
-
app.
|
41
|
+
app.options '' do
|
42
42
|
allow :get=>:index, :post=>:create
|
43
43
|
end
|
44
44
|
|
@@ -82,7 +82,7 @@ module Sinja
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
app.
|
85
|
+
app.options '/:id' do
|
86
86
|
allow :get=>:show, :patch=>:update, :delete=>:destroy
|
87
87
|
end
|
88
88
|
|
data/lib/sinja/version.rb
CHANGED
data/sinja.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ['mike@oobak.org']
|
11
11
|
|
12
12
|
spec.summary = 'RESTful, {json:api}-compliant web services in Sinatra'
|
13
|
-
spec.description = <<~EOF
|
13
|
+
spec.description = <<~'EOF'
|
14
14
|
Sinja is a Sinatra extension for quickly building RESTful,
|
15
15
|
{json:api}-compliant web services, leveraging the excellent
|
16
16
|
JSONAPI::Serializers gem for payload serialization. It enhances Sinatra's
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinja
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Pastore
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -251,6 +251,7 @@ files:
|
|
251
251
|
- demo-app/classes/post.rb
|
252
252
|
- demo-app/classes/tag.rb
|
253
253
|
- demo-app/database.rb
|
254
|
+
- lib/jsonapi/ember_serializer.rb
|
254
255
|
- lib/sinatra/jsonapi.rb
|
255
256
|
- lib/sinja.rb
|
256
257
|
- lib/sinja/config.rb
|
@@ -285,7 +286,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
285
286
|
version: '0'
|
286
287
|
requirements: []
|
287
288
|
rubyforge_project:
|
288
|
-
rubygems_version: 2.6.
|
289
|
+
rubygems_version: 2.6.10
|
289
290
|
signing_key:
|
290
291
|
specification_version: 4
|
291
292
|
summary: RESTful, {json:api}-compliant web services in Sinatra
|