sinja 0.1.0.beta3 → 0.2.0.beta1

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