sinja 1.2.3 → 1.2.4

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 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