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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4606d9daabb3be7a7aca0c603f36134f86cf2885
4
- data.tar.gz: 210cc332302ab5b34140b14bd74e24c2a72fb93a
3
+ metadata.gz: 851e8f43eabf59529257a1ef2fcc7a6df84f8ea5
4
+ data.tar.gz: b75968556128599080ba71d790d8b1671cbc89e6
5
5
  SHA512:
6
- metadata.gz: c5627d6ea47b5a62945a49b42e69f165ca10b100723b17cd3efd9b04fdc1e8b67b84e334a98b8814b90961c38958287fb1c86fce5389f9250c72cf58c234d961
7
- data.tar.gz: f1dda55b7f38c2e09f7ccb4d00fce2a09ef2342df085cd847e2937d58ccf5c5acfae2d9d004594bc12174ddf0abf0b8129e2823cbdc1124793395708e9850299
6
+ metadata.gz: 0ddd2782e3e63efab5bfee81ace29615708e08cc9a137ba28451724543f98ee3f2f6d59eda0444a0859ebd9f4b8ccdc26ed661ebde0cbdf9b902e97e18f8d23f
7
+ data.tar.gz: 1fea65b16d22c9bbb22dc0af0ed4744308eaf5dfe217da0c054da4c57b40ae5d00a36617b06d114ae2a50b324a72ac45a4c17917bd61debed1ed812d35c5e0ef
@@ -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.6.0
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.0.1
11
+ - sinatra=2.0.0.beta2 rails=5.0.1
11
12
  jdk:
12
13
  - oraclejdk8
13
14
  before_install:
14
- - gem uninstall bundler
15
- - gem install bundler -v 1.11.2
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
@@ -1,4 +1,4 @@
1
- FROM ruby:2.3-alpine
1
+ FROM ruby:2.4-alpine
2
2
  ARG container_port=4567
3
3
  ENV container_port=$container_port
4
4
 
@@ -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]
@@ -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
@@ -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 :pagination
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
@@ -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?(memoized_role)
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?(memoized_role)
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 "/#{resource_name}" do
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
 
@@ -2,6 +2,10 @@
2
2
  module Sinja
3
3
  module Helpers
4
4
  module Nested
5
+ def defer(msg=nil)
6
+ halt DEFER_CODE, msg
7
+ end
8
+
5
9
  def relationship_link?
6
10
  !params[:r].nil?
7
11
  end
@@ -19,11 +19,18 @@ module Sinja
19
19
  end
20
20
 
21
21
  def dispatch_relationship_requests!(id, methods: {}, **opts)
22
- data.fetch(:relationships, {}).each do |rel, body|
23
- rel_type = settings._resource_config[:has_one].key?(rel) ? :has_one : :has_many
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?(memoized_role))
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'][rel.to_s].tap do |linkage|
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: SecureRandom.uuid }.tap do |hash|
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
@@ -9,7 +9,7 @@ module Sinja
9
9
  app.def_action_helper(app, :merge, %i[roles sideload_on])
10
10
  app.def_action_helper(app, :subtract, :roles)
11
11
 
12
- app.head '' do
12
+ app.options '' do
13
13
  unless relationship_link?
14
14
  allow :get=>:fetch
15
15
  else
@@ -7,7 +7,7 @@ module Sinja
7
7
  app.def_action_helper(app, :prune, %i[roles sideload_on])
8
8
  app.def_action_helper(app, :graft, %i[roles sideload_on])
9
9
 
10
- app.head '' do
10
+ app.options '' do
11
11
  unless relationship_link?
12
12
  allow :get=>:pluck
13
13
  else
@@ -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?(memoized_role) ||
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.head '', :qcaptures=>{ :filter=>:id } do
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.head '' do
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.head '/:id' do
85
+ app.options '/:id' do
86
86
  allow :get=>:show, :patch=>:update, :delete=>:destroy
87
87
  end
88
88
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Sinja
3
- VERSION = '1.2.3'
3
+ VERSION = '1.2.4'
4
4
  end
@@ -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.3
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-01-11 00:00:00.000000000 Z
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.8
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