sinja 0.1.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.
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module Sinatra::JSONAPI
3
+ module RelationshipRoutes
4
+ module HasMany
5
+ ACTIONS = %i[fetch clear merge subtract].freeze
6
+ CONFLICT_ACTIONS = %i[merge].freeze
7
+
8
+ def self.registered(app)
9
+ app.def_action_helpers(ACTIONS, app)
10
+
11
+ app.get '', :actions=>:fetch do
12
+ serialize_models(*fetch)
13
+ end
14
+
15
+ app.patch '', :nullif=>proc(&:empty?), :actions=>:clear do
16
+ serialize_linkages?(*clear)
17
+ end
18
+
19
+ app.patch '', :actions=>%i[clear merge] do
20
+ clear_updated, clear_opts = clear
21
+ merge_updated, merge_opts = merge(data)
22
+ serialize_linkages?(clear_updated||merge_updated, clear_opts.merge(merge_opts)) # TODO: DWIM?
23
+ end
24
+
25
+ app.post '', :actions=>%i[merge] do
26
+ serialize_linkages?(*merge(data))
27
+ end
28
+
29
+ app.delete '', :actions=>%i[subtract] do
30
+ serialize_linkages?(*subtract(data))
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module Sinatra::JSONAPI
3
+ module RelationshipRoutes
4
+ module HasOne
5
+ ACTIONS = %i[pluck prune graft].freeze
6
+ CONFLICT_ACTIONS = %i[graft].freeze
7
+
8
+ def self.registered(app)
9
+ app.def_action_helpers(ACTIONS, app)
10
+
11
+ app.get '', :actions=>:pluck do
12
+ serialize_model(*pluck)
13
+ end
14
+
15
+ app.patch '', :nullif=>proc(&:nil?), :actions=>:prune do
16
+ serialize_linkage?(*prune)
17
+ end
18
+
19
+ app.patch '', :actions=>:graft do
20
+ serialize_linkage?(*graft(data))
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+ require 'sinatra/base'
3
+ require 'sinatra/namespace'
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'
9
+
10
+ module Sinatra::JSONAPI
11
+ module Resource
12
+ def def_action_helper(action, context=nil)
13
+ abort "JSONAPI resource actions can't be HTTP verbs!" \
14
+ if Sinatra::Base.respond_to?(action)
15
+
16
+ context.define_singleton_method(action) do |**opts, &block|
17
+ can(action, opts[:roles]) if opts.key?(:roles)
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
28
+
29
+ define_method(action) do |*args|
30
+ result =
31
+ begin
32
+ instance_exec(*args.take(block.arity.abs), &block)
33
+ 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
37
+ raise
38
+ end
39
+
40
+ # TODO: Move this to a constant or configurable?
41
+ required_arity = {
42
+ :create=>2,
43
+ :index=>-1,
44
+ :fetch=>-1
45
+ }.freeze[action] || 1
46
+
47
+ case result
48
+ when Array
49
+ opts = {}
50
+ if Hash === result.last
51
+ opts = result.pop
52
+ elsif required_arity < 0 && !(Array === result.first)
53
+ result = [result]
54
+ end
55
+
56
+ raise ActionHelperError, "Unexpected return value(s) from `#{action}' action helper" \
57
+ unless result.length == required_arity.abs
58
+
59
+ result.push(opts)
60
+ when Hash
61
+ Array.new(required_arity.abs).push(result)
62
+ else
63
+ [result, nil].take(required_arity.abs).push({})
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def def_action_helpers(actions, context=nil)
70
+ [*actions].each { |action| def_action_helper(action, context) }
71
+ end
72
+
73
+ def self.registered(app)
74
+ app.helpers Helpers::Relationships do
75
+ attr_accessor :resource
76
+
77
+ def sanity_check!(id=nil)
78
+ halt 409, 'Resource type in payload does not match endpoint' \
79
+ if data[:type] != request.path.split('/').last # TODO
80
+
81
+ halt 409, 'Resource ID in payload does not match endpoint' \
82
+ if id && data[:id].to_s != id.to_s
83
+ end
84
+ end
85
+
86
+ app.register ResourceRoutes
87
+ end
88
+
89
+ %i[has_one has_many].each do |rel_type|
90
+ define_method(rel_type) do |rel, &block|
91
+ rel_path = rel.to_s.tr('_', '-')
92
+
93
+ namespace %r{/(?<resource_id>[^/]+)(?<r>/relationships)?/#{rel_path}}, :actions=>:show do
94
+ helpers do
95
+ def relationship_link?
96
+ !params[:r].nil?
97
+ end
98
+
99
+ def resource
100
+ super || self.resource = show(params[:resource_id]).first
101
+ end
102
+
103
+ def sanity_check!
104
+ super(params[:resource_id])
105
+ end
106
+
107
+ define_method(:linkage) do
108
+ # TODO: This is extremely wasteful. Refactor JAS to expose the linkage serializer?
109
+ serialize_model(resource, :include=>rel_path)['data']['relationships'][rel_path]
110
+ end
111
+ end
112
+
113
+ before do
114
+ not_found unless resource
115
+ end
116
+
117
+ get '' do
118
+ pass unless relationship_link?
119
+
120
+ serialize_linkage
121
+ end
122
+
123
+ register RelationshipRoutes.const_get \
124
+ rel_type.to_s.split('_').map(&:capitalize).join.to_sym
125
+
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
+ instance_eval(&block) if block
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ module Sinatra::JSONAPI
3
+ module ResourceRoutes
4
+ ACTIONS = %i[index show create update destroy].freeze
5
+ CONFLICT_ACTIONS = %i[create update].freeze
6
+
7
+ def self.registered(app)
8
+ app.def_action_helpers(ACTIONS, app)
9
+
10
+ app.get '', :actions=>:index do
11
+ serialize_models(*index)
12
+ end
13
+
14
+ app.get '/:id', :actions=>:show do |id|
15
+ self.resource, opts = show(id)
16
+ not_found unless resource
17
+ serialize_model(resource, opts)
18
+ end
19
+
20
+ app.post '', :actions=>:create do
21
+ sanity_check!
22
+ halt 403, 'Client-generated IDs not supported' \
23
+ if data[:id] && method(:create).arity != 2
24
+
25
+ _, self.resource, opts = transaction do
26
+ create(data.fetch(:attributes, {}), data[:id]).tap do |id, *|
27
+ dispatch_relationship_requests!(id, :method=>:patch)
28
+ end
29
+ end
30
+
31
+ if resource
32
+ content = serialize_model(resource, opts)
33
+ if content.respond_to?(:dig) && self_link = content.dig(*%w[data links self])
34
+ headers 'Location'=>self_link
35
+ end
36
+ [201, content]
37
+ elsif data[:id]
38
+ 204
39
+ else
40
+ raise ActionHelperError, "Unexpected return value(s) from `create' action helper"
41
+ end
42
+ end
43
+
44
+ app.patch '/:id', :actions=>%i[show update] do |id|
45
+ sanity_check!(id)
46
+ self.resource, = show(id)
47
+ not_found unless resource
48
+ serialize_model?(transaction do
49
+ update(data.fetch(attributes, {})).tap do
50
+ dispatch_relationship_requests!(id, :method=>:patch)
51
+ end
52
+ end)
53
+ end
54
+
55
+ app.delete '/:id', :actions=>%i[show destroy] do |id|
56
+ self.resource, = show(id)
57
+ not_found unless resource
58
+ _, opts = destroy
59
+ serialize_model?(nil, opts)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ module Sinatra
3
+ module JSONAPI
4
+ MIME_TYPE = 'application/vnd.api+json'
5
+
6
+ SinjaError = Class.new(StandardError)
7
+ ActionHelperError = Class.new(SinjaError)
8
+ end
9
+ end
10
+
11
+ Sinja = Sinatra::JSONAPI
12
+
13
+ require 'sinatra/jsonapi'
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: false
2
+ module Sinatra
3
+ module JSONAPI
4
+ VERSION = '0.1.0.beta1'.freeze
5
+ end
6
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sinja/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'sinja'
8
+ spec.version = Sinatra::JSONAPI::VERSION
9
+ spec.authors = ['Mike Pastore']
10
+ spec.email = ['mike@oobak.org']
11
+
12
+ spec.summary = 'RESTful, JSON:API-compliant web services in Sinatra'
13
+ spec.homepage = 'https://github.com/mwpastore/sinja'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.require_paths = %w[lib]
20
+
21
+ # TODO: relax these dependencies
22
+ spec.add_dependency 'json', '~> 2.0.1'
23
+ spec.add_dependency 'jsonapi-serializers', '~> 0.16.0'
24
+ spec.add_dependency 'sinatra', '~> 2.0.0.beta2'
25
+ spec.add_dependency 'sinatra-contrib', '~> 2.0.0.beta2'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.13'
28
+ spec.add_development_dependency 'rake', '~> 11.3'
29
+ spec.add_development_dependency 'rspec', '~> 3.0'
30
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinja
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.beta1
5
+ platform: ruby
6
+ authors:
7
+ - Mike Pastore
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: jsonapi-serializers
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.16.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.16.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: sinatra
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.0.0.beta2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.0.0.beta2
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra-contrib
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.0.beta2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.0.beta2
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.13'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.13'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '11.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '11.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ description:
112
+ email:
113
+ - mike@oobak.org
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/console
126
+ - bin/setup
127
+ - 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
+ - lib/sinja.rb
137
+ - lib/sinja/version.rb
138
+ - sinja.gemspec
139
+ homepage: https://github.com/mwpastore/sinja
140
+ licenses:
141
+ - MIT
142
+ metadata: {}
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">"
155
+ - !ruby/object:Gem::Version
156
+ version: 1.3.1
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 2.5.1
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: RESTful, JSON:API-compliant web services in Sinatra
163
+ test_files: []