sinja 0.1.0.beta3 → 0.2.0.beta1

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: e15669879c360c6e547ed1c4a054ab5e2003cdcf
4
- data.tar.gz: 5a8bde26b795e72994009a09028c8d9a94421e6a
3
+ metadata.gz: 3a2a92548635cd6db938186715c337d3d19bec29
4
+ data.tar.gz: c31184c7f6c8c33698096846a986dce1e4ec53cc
5
5
  SHA512:
6
- metadata.gz: 449e36f9a4eedf5152bc3d2b5dfcc291ee015c2fa322534dc0764b00200fd998d5c4324203f4dd42586d6268ad76502042bccaf54a585403c9f599cdb629e528
7
- data.tar.gz: e8a7652d029cc81beff40fd9c5e9fc2a3b55a959cbf9aa8d643c883d4990d4408a0f3e6b550591a7e4ea361e5d0140bd69167aa691466643d6a35e901fd5c549
6
+ metadata.gz: 5e0ec4ee3a7b6bf7fa0ffb66fc0cde0cb736be5a16ba3ef1dcba40bd0d6b1bd8b378aa640c3dc21df7d439f5b0d78a9e0ea8ba05c6b0ecd4621ff9b0ac2f1c82
7
+ data.tar.gz: 1db8dcd6991dc3f89540a8e5e3a3230d42a210d1951a7fadbdb13b561a38727231575f36ad4b7351f78b4b78644bbdff66f74ba58d6ce2f8c163ed7fc3798c8a
data/README.md CHANGED
@@ -55,6 +55,7 @@ has not yet been thoroughly tested or vetted in a production environment.**
55
55
  - [`role` helper](#role-helper)
56
56
  - [Conflicts](#conflicts)
57
57
  - [Transactions](#transactions)
58
+ - [Coalesced Find Requests](#coalesced-find-requests)
58
59
  - [Module Namespaces](#module-namespaces)
59
60
  - [Code Organization](#code-organization)
60
61
  - [Development](#development)
@@ -280,8 +281,6 @@ end
280
281
 
281
282
  **dedasherize_names**
282
283
  : Takes a hash and returns the hash with its keys dedasherized (deeply).
283
- Useful for fixing up the hashes of attributes passed to the `create` and
284
- `update` action helpers before they are passed on to ORM methods.
285
284
 
286
285
  ### Performance
287
286
 
@@ -356,7 +355,7 @@ these settings.
356
355
  by manually `use`-ing the Rack::Protection middleware)
357
356
  * Disables static file routes (can be reenabled with `enable :static`)
358
357
  * Sets `:show_exceptions` to `:after_handler`
359
- * Adds an `:api_json` MIME-type (`Sinatra::JSONAPI::MIME_TYPE`)
358
+ * Adds an `:api_json` MIME-type (`Sinja::MIME_TYPE`)
360
359
  * Enforces strict checking of the `Accept` and `Content-Type` request headers
361
360
  * Sets the `Content-Type` response header to `:api_json` (can be overriden with
362
361
  the `content_type` helper)
@@ -408,7 +407,9 @@ to JSONAPI::Serializers. You may also use the special `:exclude` option to
408
407
  prevent specific relationships from being included in the response. This
409
408
  accepts the same formats as JSONAPI::Serializers does for `:include`. If you
410
409
  exclude a relationship, any sub-relationships will also be excluded. The
411
- `:sort`, `:page`, and `:filter` query parameters must be handled manually.
410
+ `:sort`, `:page`, and `:filter` query parameters must be handled manually (with
411
+ the exception of the `:id` filter, discussed under "Coalesced Find Requests"
412
+ below).
412
413
 
413
414
  All arguments to action helpers are "tainted" and should be treated as
414
415
  potentially dangerous: IDs, attribute hashes, and [resource identifier
@@ -436,22 +437,22 @@ serialize on the response.
436
437
 
437
438
  ##### `create {|attr, id| ..}` => id, Object?
438
439
 
439
- With client-generated IDs: Take a hash of attributes and a client-generated ID,
440
- create a new resource, and return the ID and optionally the created resource.
441
- (Note that only one or the other `create` action helpers is allowed in any
442
- given resource block.)
440
+ With client-generated IDs: Take a hash of (dedasherized) attributes and a
441
+ client-generated ID, create a new resource, and return the ID and optionally
442
+ the created resource. (Note that only one or the other `create` action helpers
443
+ is allowed in any given resource block.)
443
444
 
444
445
  ##### `create {|attr| ..}` => id, Object
445
446
 
446
- Without client-generated IDs: Take a hash of attributes, create a new resource,
447
- and return the server-generated ID and the created resource. (Note that only
448
- one or the other `create` action helpers is allowed in any given resource
449
- block.)
447
+ Without client-generated IDs: Take a hash of (dedasherized) attributes, create
448
+ a new resource, and return the server-generated ID and the created resource.
449
+ (Note that only one or the other `create` action helpers is allowed in any
450
+ given resource block.)
450
451
 
451
452
  ##### `update {|attr| ..}` => Object?
452
453
 
453
- Take a hash of attributes, update `resource`, and optionally return the updated
454
- resource.
454
+ Take a hash of (dedasherized) attributes, update `resource`, and optionally
455
+ return the updated resource.
455
456
 
456
457
  ##### `destroy {..}`
457
458
 
@@ -462,8 +463,7 @@ Delete or destroy `resource`.
462
463
  ##### `pluck {..}` => Object
463
464
 
464
465
  Return the related object vis-à-vis `resource` to serialize on the
465
- response. Defined by default as `resource.send(<to-one>)`; can be either
466
- overridden or disabled entirely with `pluck(&nil)`.
466
+ response.
467
467
 
468
468
  ##### `prune {..}` => TrueClass?
469
469
 
@@ -493,8 +493,7 @@ Take a [resource identifier object][22] and update the relationship on
493
493
  ##### `fetch {..}` => Array
494
494
 
495
495
  Return an array of related objects vis-&agrave;-vis `resource` to serialize on
496
- the response. Defined by default as `resource.send(<to-many>)`; can be either
497
- overridden or disabled entirely with `fetch(&nil)`.
496
+ the response.
498
497
 
499
498
  ##### `clear {..}` => TrueClass?
500
499
 
@@ -614,6 +613,23 @@ helpers do
614
613
  end
615
614
  ```
616
615
 
616
+ If you need more fine-grained control, for example if your action helper logic
617
+ varies by the user's role, you can use a simple switch statement along with the
618
+ `RoleList` utility class:
619
+
620
+ ```ruby
621
+ index(roles: []) do
622
+ case role
623
+ when RoleList[:user]
624
+ # logic specific to user role
625
+ when RoleList[:admin, :super]
626
+ # logic specific to administrative roles
627
+ else
628
+ halt 403, 'Access denied!'
629
+ end
630
+ end
631
+ ```
632
+
617
633
  ### Conflicts
618
634
 
619
635
  If your database driver raises exceptions on constraint violations, you should
@@ -649,6 +665,15 @@ helpers do
649
665
  end
650
666
  ```
651
667
 
668
+ ### Coalesced Find Requests
669
+
670
+ If your JSON:API client coalesces find requests, the `show` action helper will
671
+ be invoked once for each ID in the `:id` filter, and the resulting collection
672
+ will be serialized on the response. Both query parameter syntaxes for arrays
673
+ are supported: `?filter[id]=1,2` and `?filter[id][]=1&filter[id][]=2`. If any
674
+ ID is not found (i.e. `show` returns `nil`), the route will halt with HTTP
675
+ status code 404.
676
+
652
677
  ### Module Namespaces
653
678
 
654
679
  Everything is dual-namespaced under both Sinatra::JSONAPI and Sinja, and Sinja
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require 'sinatra/jsonapi/resource'
4
+ require 'sinja'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
data/lib/role_list.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+
4
+ class RoleList < Set
5
+ def ===(other)
6
+ self.intersect?(self.class === other ? other : self.class[*other])
7
+ end
8
+ end
@@ -1,134 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require 'set'
3
- require 'sinatra/base'
4
- require 'sinatra/namespace'
5
-
6
2
  require 'sinja'
7
- require 'sinja/version'
8
-
9
- require 'sinatra/jsonapi/config'
10
- require 'sinatra/jsonapi/helpers/serializers'
11
- require 'sinatra/jsonapi/resource'
12
-
13
- module Sinatra::JSONAPI
14
- def resource(resource_name, konst=nil, &block)
15
- abort "Must supply proc constant or block for `resource'" \
16
- unless block = konst and konst.is_a?(Proc) or block
17
-
18
- sinja_config.resource_roles[resource_name.to_sym] # trigger default proc
19
-
20
- namespace "/#{resource_name.to_s.tr('_', '-')}" do
21
- define_singleton_method(:can) do |action, roles|
22
- sinja_config.resource_roles[resource_name.to_sym].merge!(action=>roles)
23
- end
24
-
25
- helpers do
26
- define_method(:can?) do |*args|
27
- super(resource_name.to_sym, *args)
28
- end
29
- end
30
-
31
- register Resource
32
-
33
- instance_eval(&block)
34
- end
35
- end
36
-
37
- def sinja
38
- if block_given?
39
- yield sinja_config
40
- else
41
- sinja_config
42
- end
43
- end
44
-
45
- alias_method :configure_jsonapi, :sinja
46
- def freeze_jsonapi
47
- sinja_config.freeze
48
- end
49
-
50
- def self.registered(app)
51
- app.register Sinatra::Namespace
52
-
53
- app.disable :protection, :static
54
- app.set :sinja_config, Sinatra::JSONAPI::Config.new
55
- app.configure(:development) do |c|
56
- c.set :show_exceptions, :after_handler
57
- end
58
-
59
- app.set :actions do |*actions|
60
- condition do
61
- actions.each do |action|
62
- halt 403, 'You are not authorized to perform this action' unless can?(action)
63
- halt 405, 'Action or method not implemented or supported' unless respond_to?(action)
64
- end
65
- true
66
- end
67
- end
68
-
69
- app.set :nullif do |nullish|
70
- condition { nullish.(data) }
71
- end
72
-
73
- app.mime_type :api_json, MIME_TYPE
74
-
75
- app.helpers Helpers::Serializers do
76
- def can?(resource_name, action)
77
- roles = settings.sinja_config.resource_roles[resource_name][action]
78
- roles.nil? || roles.empty? || Set[*role].intersect?(roles)
79
- end
80
-
81
- def content?
82
- request.body.respond_to?(:size) && request.body.size > 0
83
- end
84
-
85
- def data
86
- @data ||= deserialized_request_body[:data]
87
- end
88
-
89
- def normalize_params!
90
- # TODO: halt 400 if other params, or params not implemented?
91
- {
92
- :fields=>{}, # passthru
93
- :include=>[], # passthru
94
- :filter=>{},
95
- :page=>{},
96
- :sort=>''
97
- }.each { |k, v| params[k] ||= v }
98
- end
99
-
100
- def role
101
- nil
102
- end
103
-
104
- def transaction
105
- yield
106
- end
107
- end
108
-
109
- app.before do
110
- halt 406 unless request.preferred_type.entry == MIME_TYPE
111
-
112
- if content?
113
- halt 415 unless request.media_type == MIME_TYPE
114
- halt 415 if request.media_type_params.keys.any? { |k| k != 'charset' }
115
- end
116
-
117
- content_type :api_json
118
-
119
- normalize_params!
120
- end
121
-
122
- app.after do
123
- body serialized_response_body if response.ok?
124
- end
125
-
126
- app.error 400...600, nil do
127
- serialized_error
128
- end
129
- end
130
- end
131
3
 
132
4
  module Sinatra
133
- register JSONAPI
5
+ register JSONAPI = Sinja
134
6
  end
data/lib/sinja.rb CHANGED
@@ -1,13 +1,140 @@
1
1
  # frozen_string_literal: true
2
- module Sinatra
3
- module JSONAPI
4
- MIME_TYPE = 'application/vnd.api+json'
2
+ require 'sinatra/base'
3
+ require 'sinatra/namespace'
5
4
 
6
- SinjaError = Class.new(StandardError)
7
- ActionHelperError = Class.new(SinjaError)
5
+ require 'role_list'
6
+ require 'sinja/config'
7
+ require 'sinja/helpers/serializers'
8
+ require 'sinja/resource'
9
+
10
+ module Sinja
11
+ MIME_TYPE = 'application/vnd.api+json'
12
+
13
+ SinjaError = Class.new(StandardError)
14
+ ActionHelperError = Class.new(SinjaError)
15
+
16
+ def resource(resource_name, konst=nil, &block)
17
+ abort "Must supply proc constant or block for `resource'" \
18
+ unless block = (konst and konst.is_a?(Proc) or block)
19
+
20
+ _sinja.resource_roles[resource_name.to_sym] # trigger default proc
21
+
22
+ namespace "/#{resource_name.to_s.tr('_', '-')}" do
23
+ define_singleton_method(:can) do |action, roles|
24
+ _sinja.resource_roles[resource_name.to_sym].merge!(action=>roles)
25
+ end
26
+
27
+ helpers do
28
+ define_method(:can?) do |*args|
29
+ super(resource_name.to_sym, *args)
30
+ end
31
+ end
32
+
33
+ register Resource
34
+
35
+ instance_eval(&block)
36
+ end
8
37
  end
9
- end
10
38
 
11
- Sinja = Sinatra::JSONAPI
39
+ def sinja
40
+ if block_given?
41
+ yield _sinja
42
+ else
43
+ _sinja
44
+ end
45
+ end
46
+
47
+ alias_method :configure_jsonapi, :sinja
48
+ def freeze_jsonapi
49
+ _sinja.freeze
50
+ end
51
+
52
+ def self.registered(app)
53
+ app.register Sinatra::Namespace
54
+
55
+ app.disable :protection, :static
56
+ app.set :_sinja, Sinja::Config.new
57
+ app.configure(:development) do |c|
58
+ c.set :show_exceptions, :after_handler
59
+ end
60
+
61
+ app.set :actions do |*actions|
62
+ condition do
63
+ actions.each do |action|
64
+ halt 403, 'You are not authorized to perform this action' unless can?(action)
65
+ halt 405, 'Action or method not implemented or supported' unless respond_to?(action)
66
+ end
67
+ true
68
+ end
69
+ end
70
+
71
+ app.set :pfilters do |*pfilters|
72
+ condition do
73
+ pfilters.all? do |pfilter|
74
+ params.key?('filter') && params['filter'].key?(pfilter.to_s)
75
+ end
76
+ end
77
+ end
12
78
 
13
- require 'sinatra/jsonapi'
79
+ app.set :nullif do |nullish|
80
+ condition { nullish.(data) }
81
+ end
82
+
83
+ app.mime_type :api_json, MIME_TYPE
84
+
85
+ app.helpers Helpers::Serializers do
86
+ def can?(resource_name, action)
87
+ roles = settings._sinja.resource_roles[resource_name][action]
88
+ roles.nil? || roles.empty? || roles === role
89
+ end
90
+
91
+ def content?
92
+ request.body.respond_to?(:size) && request.body.size > 0
93
+ end
94
+
95
+ def data
96
+ @data ||= deserialized_request_body[:data]
97
+ end
98
+
99
+ def normalize_params!
100
+ # TODO: halt 400 if other params, or params not implemented?
101
+ {
102
+ :fields=>{}, # passthru
103
+ :include=>[], # passthru
104
+ :filter=>{},
105
+ :page=>{},
106
+ :sort=>''
107
+ }.each { |k, v| params[k] ||= v }
108
+ end
109
+
110
+ def role
111
+ nil
112
+ end
113
+
114
+ def transaction
115
+ yield
116
+ end
117
+ end
118
+
119
+ app.before do
120
+ halt 406 unless request.preferred_type.entry == MIME_TYPE
121
+
122
+ if content?
123
+ halt 415 unless request.media_type == MIME_TYPE
124
+ halt 415 if request.media_type_params.keys.any? { |k| k != 'charset' }
125
+ end
126
+
127
+ content_type :api_json
128
+
129
+ normalize_params!
130
+ end
131
+
132
+ app.after do
133
+ body serialized_response_body if response.ok?
134
+ end
135
+
136
+ app.error 400...600, nil do
137
+ serialized_error
138
+ end
139
+ end
140
+ end
@@ -2,11 +2,12 @@
2
2
  require 'forwardable'
3
3
  require 'set'
4
4
 
5
- require 'sinatra/jsonapi/relationship_routes/has_many'
6
- require 'sinatra/jsonapi/relationship_routes/has_one'
7
- require 'sinatra/jsonapi/resource_routes'
5
+ require 'role_list'
6
+ require 'sinja/relationship_routes/has_many'
7
+ require 'sinja/relationship_routes/has_one'
8
+ require 'sinja/resource_routes'
8
9
 
9
- module Sinatra::JSONAPI
10
+ module Sinja
10
11
  module ConfigUtils
11
12
  def deep_copy(c)
12
13
  Marshal.load(Marshal.dump(c))
@@ -98,7 +99,7 @@ module Sinatra::JSONAPI
98
99
  ResourceRoutes::ACTIONS,
99
100
  RelationshipRoutes::HasMany::ACTIONS,
100
101
  RelationshipRoutes::HasOne::ACTIONS
101
- ].reduce([], :concat).map { |action| [action, Set.new] }.to_h
102
+ ].reduce([], :concat).map { |action| [action, RoleList.new] }.to_h
102
103
  end
103
104
 
104
105
  def_delegator :@data, :[]
@@ -107,7 +108,7 @@ module Sinatra::JSONAPI
107
108
  h.each do |action, roles|
108
109
  abort "Unknown or invalid action helper `#{action}' in configuration" \
109
110
  unless @data.key?(action)
110
- @data[action].replace(Set[*roles])
111
+ @data[action].replace(RoleList[*roles])
111
112
  end
112
113
  @data
113
114
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'json'
3
3
 
4
- module Sinatra::JSONAPI
4
+ module Sinja
5
5
  module Helpers
6
6
  module Relationships
7
7
  def dispatch_relationship_request(id, path, **opts)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'sequel/model/inflections'
3
3
 
4
- module Sinatra::JSONAPI
4
+ module Sinja
5
5
  module Helpers
6
6
  module Sequel
7
7
  include ::Sequel::Inflections
@@ -26,12 +26,12 @@ module Sinatra::JSONAPI
26
26
  [resource.pk, resource, opts]
27
27
  end
28
28
 
29
- # <= association, rios, extra_keys
29
+ # <= association, rios, block
30
30
  def add_missing(*args)
31
31
  add_remove(:add, :-, *args)
32
32
  end
33
33
 
34
- # <= association, rios, extra_keys
34
+ # <= association, rios, block
35
35
  def remove_present(*args)
36
36
  add_remove(:remove, :&, *args)
37
37
  end
@@ -42,10 +42,8 @@ module Sinatra::JSONAPI
42
42
  meth = "#{meth_prefix}_#{singularize(association)}".to_sym
43
43
  transaction do
44
44
  resource.lock!
45
- venn(operator, association, rios) do |subresource, rio|
46
- args = [subresource]
47
- args.push(yield(rio)) if block_given?
48
- resource.send(meth, *args)
45
+ venn(operator, association, rios) do |subresource|
46
+ resource.send(meth, subresource)
49
47
  end
50
48
  resource.reload
51
49
  end
@@ -53,11 +51,10 @@ module Sinatra::JSONAPI
53
51
 
54
52
  def venn(operator, association, rios)
55
53
  dataset = resource.send("#{association}_dataset")
56
- klass = resource.class.association_reflection(association)
57
- rios = rios.map { |rio| [rio[:id], rio] }.to_h
58
- rios.keys.send(operator, dataset.select_map(klass.primary_key)).each do |id|
59
- yield klass.with_pk!(id), rios[id]
60
- end
54
+ # does not / will not work with composite primary keys
55
+ rios.map { |rio| rio[:id].to_i }
56
+ .send(operator, dataset.select_map(:id))
57
+ .each { |id| yield dataset.with_pk!(id) }
61
58
  end
62
59
  end
63
60
  end
@@ -3,7 +3,7 @@ require 'json'
3
3
  require 'jsonapi-serializers'
4
4
  require 'set'
5
5
 
6
- module Sinatra::JSONAPI
6
+ module Sinja
7
7
  module Helpers
8
8
  module Serializers
9
9
  def dedasherize(s=nil)
@@ -32,7 +32,7 @@ module Sinatra::JSONAPI
32
32
  end
33
33
 
34
34
  def serialized_response_body
35
- JSON.send settings.sinja_config.json_generator, response.body
35
+ JSON.send settings._sinja.json_generator, response.body
36
36
  rescue JSON::GeneratorError
37
37
  halt 400, 'Unserializable entities in the response body'
38
38
  end
@@ -64,7 +64,7 @@ module Sinatra::JSONAPI
64
64
  exclude!(options) if options[:include] && options[:exclude]
65
65
 
66
66
  ::JSONAPI::Serializer.serialize model,
67
- settings.sinja_config.serializer_opts.merge(options)
67
+ settings._sinja.serializer_opts.merge(options)
68
68
  end
69
69
 
70
70
  def serialize_model?(model=nil, options={})
@@ -87,7 +87,7 @@ module Sinatra::JSONAPI
87
87
  exclude!(options) if options[:include] && options[:exclude]
88
88
 
89
89
  ::JSONAPI::Serializer.serialize [*models],
90
- settings.sinja_config.serializer_opts.merge(options)
90
+ settings._sinja.serializer_opts.merge(options)
91
91
  end
92
92
 
93
93
  def serialize_models?(models=[], options={})
@@ -101,7 +101,7 @@ module Sinatra::JSONAPI
101
101
  end
102
102
 
103
103
  def serialize_linkage(options={})
104
- options = settings.sinja_config.serializer_opts.merge(options)
104
+ options = settings._sinja.serializer_opts.merge(options)
105
105
  linkage.tap do |c|
106
106
  c[:meta] = options[:meta] if options.key?(:meta)
107
107
  c[:jsonapi] = options[:jsonapi] if options.key?(:jsonapi)
@@ -142,8 +142,8 @@ module Sinatra::JSONAPI
142
142
 
143
143
  def serialized_error
144
144
  hash = error_hash(normalized_error)
145
- logger.error(settings.sinja_config.logger_progname) { hash }
146
- JSON.send settings.sinja_config.json_error_generator,
145
+ logger.error(settings._sinja.logger_progname) { hash }
146
+ JSON.send settings._sinja.json_error_generator,
147
147
  ::JSONAPI::Serializer.serialize_errors([hash])
148
148
  end
149
149
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- module Sinatra::JSONAPI
2
+ module Sinja
3
3
  module RelationshipRoutes
4
4
  module HasMany
5
5
  ACTIONS = %i[fetch clear merge subtract].freeze
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- module Sinatra::JSONAPI
2
+ module Sinja
3
3
  module RelationshipRoutes
4
4
  module HasOne
5
5
  ACTIONS = %i[pluck prune graft].freeze
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ require 'sinja/helpers/sequel'
3
+
4
+ module Sinja
5
+ module Resource
6
+ alias_method :core_has_one, :has_one
7
+
8
+ def has_one(rel, &block)
9
+ core_has_one(rel) do
10
+ pluck do
11
+ resource.send(rel)
12
+ end
13
+
14
+ prune do
15
+ resource.send("#{rel}=", nil)
16
+ resource.save_changes
17
+ end
18
+
19
+ graft do |rio|
20
+ klass = resource.class.association_reflection(rel).associated_class
21
+ resource.send("#{rel}=", klass.with_pk!(rio[:id]))
22
+ resource.save_changes
23
+ end
24
+
25
+ instance_eval(&block) if block
26
+ end
27
+ end
28
+
29
+ alias_method :core_has_many, :has_many
30
+
31
+ def has_many(rel, &block)
32
+ core_has_many(rel) do
33
+ fetch do
34
+ resource.send(rel)
35
+ end
36
+
37
+ clear do
38
+ resource.send("remove_all_#{rel}")
39
+ end
40
+
41
+ merge do |rios|
42
+ add_missing(rel, rios)
43
+ end
44
+
45
+ subtract do |rios|
46
+ remove_present(rel, rios)
47
+ end
48
+
49
+ instance_eval(&block) if block
50
+ end
51
+ end
52
+ end
53
+ end
@@ -2,12 +2,12 @@
2
2
  require 'sinatra/base'
3
3
  require 'sinatra/namespace'
4
4
 
5
- require 'sinatra/jsonapi/helpers/relationships'
6
- require 'sinatra/jsonapi/relationship_routes/has_many'
7
- require 'sinatra/jsonapi/relationship_routes/has_one'
8
- require 'sinatra/jsonapi/resource_routes'
5
+ require 'sinja/helpers/relationships'
6
+ require 'sinja/relationship_routes/has_many'
7
+ require 'sinja/relationship_routes/has_one'
8
+ require 'sinja/resource_routes'
9
9
 
10
- module Sinatra::JSONAPI
10
+ module Sinja
11
11
  module Resource
12
12
  def def_action_helper(action, context=nil)
13
13
  abort "JSONAPI resource actions can't be HTTP verbs!" \
@@ -16,24 +16,18 @@ module Sinatra::JSONAPI
16
16
  context.define_singleton_method(action) do |**opts, &block|
17
17
  can(action, opts[:roles]) if opts.key?(:roles)
18
18
 
19
- if block.nil?
20
- if respond_to?(action)
21
- # TODO: Is this safe?
22
- remove_method(action)
23
- return
24
- else
25
- # TODO: Throw an error?
26
- end
27
- end
19
+ return if block.nil?
28
20
 
29
21
  define_method(action) do |*args|
22
+ send("before_#{action}") if respond_to?("before_#{action}")
23
+
30
24
  result =
31
25
  begin
32
26
  instance_exec(*args.take(block.arity.abs), &block)
33
27
  rescue Exception=>e
34
- halt 409, e.message if settings.sinja_config.conflict?(action, e.class)
35
- #halt 422, resource.errors if settings.sinja_config.invalid?(action, e.class) # TODO
36
- #not_found if settings.sinja_config.not_found?(action, e.class) # TODO
28
+ halt 409, e.message if settings._sinja.conflict?(action, e.class)
29
+ #halt 422, resource.errors if settings._sinja.invalid?(action, e.class) # TODO
30
+ #not_found if settings._sinja.not_found?(action, e.class) # TODO
37
31
  raise
38
32
  end
39
33
 
@@ -63,6 +57,10 @@ module Sinatra::JSONAPI
63
57
  [result, nil].take(required_arity.abs).push({})
64
58
  end
65
59
  end
60
+
61
+ define_singleton_method("remove_#{action}") do
62
+ remove_method(action)
63
+ end
66
64
  end
67
65
  end
68
66
 
@@ -74,6 +72,10 @@ module Sinatra::JSONAPI
74
72
  app.helpers Helpers::Relationships do
75
73
  attr_accessor :resource
76
74
 
75
+ def attributes
76
+ dedasherize_names(data.fetch(:attributes, {}))
77
+ end
78
+
77
79
  def sanity_check!(id=nil)
78
80
  halt 409, 'Resource type in payload does not match endpoint' \
79
81
  if data[:type] != request.path.split('/').last # TODO
@@ -123,12 +125,6 @@ module Sinatra::JSONAPI
123
125
  register RelationshipRoutes.const_get \
124
126
  rel_type.to_s.split('_').map(&:capitalize).join.to_sym
125
127
 
126
- if rel_type == :has_one
127
- pluck { resource.send(rel) }
128
- elsif rel_type == :has_many
129
- fetch { resource.send(rel) }
130
- end
131
-
132
128
  instance_eval(&block) if block
133
129
  end
134
130
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- module Sinatra::JSONAPI
2
+ module Sinja
3
3
  module ResourceRoutes
4
4
  ACTIONS = %i[index show create update destroy].freeze
5
5
  CONFLICT_ACTIONS = %i[create update].freeze
@@ -7,6 +7,21 @@ module Sinatra::JSONAPI
7
7
  def self.registered(app)
8
8
  app.def_action_helpers(ACTIONS, app)
9
9
 
10
+ app.get '', :pfilters=>:id, :actions=>:show do
11
+ ids = params['filter'].delete('id')
12
+ ids = ids.split(',') if ids.respond_to?(:split)
13
+
14
+ opts = {}
15
+ resources = [*ids].tap(&:uniq!).map! do |id|
16
+ self.resource, opts = show(id)
17
+ not_found "Resource '#{id}' not found" unless resource
18
+ resource
19
+ end
20
+
21
+ # TODO: Serialize collection with opts from last model found?
22
+ serialize_models(resources, opts)
23
+ end
24
+
10
25
  app.get '', :actions=>:index do
11
26
  serialize_models(*index)
12
27
  end
@@ -23,7 +38,7 @@ module Sinatra::JSONAPI
23
38
  if data[:id] && method(:create).arity != 2
24
39
 
25
40
  _, self.resource, opts = transaction do
26
- create(data.fetch(:attributes, {}), data[:id]).tap do |id, *|
41
+ create(attributes, data[:id]).tap do |id, *|
27
42
  dispatch_relationship_requests!(id, :method=>:patch)
28
43
  end
29
44
  end
@@ -46,7 +61,7 @@ module Sinatra::JSONAPI
46
61
  self.resource, = show(id)
47
62
  not_found "Resource '#{id}' not found" unless resource
48
63
  serialize_model?(transaction do
49
- update(data.fetch(attributes, {})).tap do
64
+ update(attributes).tap do
50
65
  dispatch_relationship_requests!(id, :method=>:patch)
51
66
  end
52
67
  end)
data/lib/sinja/version.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  # frozen_string_literal: false
2
- module Sinatra
3
- module JSONAPI
4
- VERSION = '0.1.0.beta3'.freeze
5
- end
2
+ module Sinja
3
+ VERSION = '0.2.0.beta1'.freeze
6
4
  end
data/sinja.gemspec CHANGED
@@ -5,7 +5,7 @@ require 'sinja/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'sinja'
8
- spec.version = Sinatra::JSONAPI::VERSION
8
+ spec.version = Sinja::VERSION
9
9
  spec.authors = ['Mike Pastore']
10
10
  spec.email = ['mike@oobak.org']
11
11
 
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: 0.1.0.beta3
4
+ version: 0.2.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Pastore
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-10 00:00:00.000000000 Z
11
+ date: 2016-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -124,16 +124,18 @@ files:
124
124
  - Rakefile
125
125
  - bin/console
126
126
  - bin/setup
127
+ - lib/role_list.rb
127
128
  - lib/sinatra/jsonapi.rb
128
- - lib/sinatra/jsonapi/config.rb
129
- - lib/sinatra/jsonapi/helpers/relationships.rb
130
- - lib/sinatra/jsonapi/helpers/sequel.rb
131
- - lib/sinatra/jsonapi/helpers/serializers.rb
132
- - lib/sinatra/jsonapi/relationship_routes/has_many.rb
133
- - lib/sinatra/jsonapi/relationship_routes/has_one.rb
134
- - lib/sinatra/jsonapi/resource.rb
135
- - lib/sinatra/jsonapi/resource_routes.rb
136
129
  - lib/sinja.rb
130
+ - lib/sinja/config.rb
131
+ - lib/sinja/helpers/relationships.rb
132
+ - lib/sinja/helpers/sequel.rb
133
+ - lib/sinja/helpers/serializers.rb
134
+ - lib/sinja/relationship_routes/has_many.rb
135
+ - lib/sinja/relationship_routes/has_one.rb
136
+ - lib/sinja/relationship_routes/sequel.rb
137
+ - lib/sinja/resource.rb
138
+ - lib/sinja/resource_routes.rb
137
139
  - lib/sinja/version.rb
138
140
  - sinja.gemspec
139
141
  homepage: https://github.com/mwpastore/sinja