sinja 1.0.0.pre2 → 1.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +313 -107
- data/demo-app/README.md +58 -0
- data/demo-app/app.rb +8 -4
- data/demo-app/boot.rb +3 -1
- data/demo-app/classes/author.rb +18 -15
- data/demo-app/classes/comment.rb +11 -9
- data/demo-app/classes/post.rb +21 -16
- data/demo-app/classes/tag.rb +12 -8
- data/demo-app/database.rb +4 -6
- data/lib/sinja/config.rb +85 -76
- data/lib/sinja/helpers/relationships.rb +5 -3
- data/lib/sinja/helpers/sequel.rb +43 -0
- data/lib/sinja/helpers/serializers.rb +39 -20
- data/lib/sinja/relationship_routes/has_many.rb +9 -5
- data/lib/sinja/relationship_routes/has_one.rb +4 -4
- data/lib/sinja/resource.rb +19 -21
- data/lib/sinja/resource_routes.rb +33 -20
- data/lib/sinja/version.rb +1 -1
- data/lib/sinja.rb +137 -48
- data/sinja.gemspec +1 -1
- metadata +4 -4
- data/demo-app/test.rb +0 -17
data/demo-app/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
## Demo App
|
2
|
+
|
3
|
+
This is the demo app for Sinja, used as an example of and for testing Sinja. It
|
4
|
+
is a very simplistic blog-like application with database tables, models,
|
5
|
+
serializers, and controllers for authors, posts, comments, and tags. It uses
|
6
|
+
[Sequel ORM](http://sequel.jeremyevans.net) (and the [Sequel
|
7
|
+
helpers](/lib/sinja/helpers/sequel.rb) provided with Sinja) with an in-memory
|
8
|
+
SQLite database, and it works under both MRI/YARV 2.3+ and JRuby 9.1+.
|
9
|
+
|
10
|
+
### Usage
|
11
|
+
|
12
|
+
Assuming you have a working, Bundler-enabled Ruby environment, simply clone
|
13
|
+
this repo, `cd` into the `demo-app` subdirectory, and run the following
|
14
|
+
commands:
|
15
|
+
|
16
|
+
```
|
17
|
+
$ bundle install
|
18
|
+
$ bundle exec ruby app.rb [-p <PORT>]
|
19
|
+
```
|
20
|
+
|
21
|
+
The web server will report the port it's listening on, or you can specify a
|
22
|
+
port with the `-p` option. It will respond to {json:api}-compliant requests
|
23
|
+
(don't forget to set an `Accept` header) to `/authors`, `/posts`, `/comments`,
|
24
|
+
and `/tags`, although not every endpoint is implemented. Log in by setting the
|
25
|
+
`X-Email` header on the request to the email address of a registered user; the
|
26
|
+
default administrator email address is all@yourbase.com.
|
27
|
+
|
28
|
+
**This is clearly extremely insecure and should not be used as-is in production.
|
29
|
+
Caveat emptor.**
|
30
|
+
|
31
|
+
You can point it at a different database by setting `DATABASE_URL` in the
|
32
|
+
environment before executing `app.rb`. See the relevant [Sequel
|
33
|
+
documentation](http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html)
|
34
|
+
for more information. It (rather naïvely) migrates the database at
|
35
|
+
startup.
|
36
|
+
|
37
|
+
### Productionalizing
|
38
|
+
|
39
|
+
You can certainly use this as a starting point for a production application,
|
40
|
+
but you will at least want to:
|
41
|
+
|
42
|
+
- [ ] Use a persistent database
|
43
|
+
- [ ] Separate the class files (e.g. `author.rb`, `post.rb`) into separate
|
44
|
+
files for the migrations, models, serializers, and Sinja controllers
|
45
|
+
- [ ] Create a Gemfile using the dependencies in the top-level
|
46
|
+
[gemspec](/sinja.gemspec) as a starting point
|
47
|
+
- [ ] Add authentication middleware and rewrite the `role` helper to enable
|
48
|
+
the authorization scheme. You can use the existing roles as defined or
|
49
|
+
rename them (e.g. use `:admin` instead of `:superuser`)
|
50
|
+
- [ ] Use a real application server such as [Puma](http://puma.io) or
|
51
|
+
[Passenger](https://www.phusionpassenger.com) instead of Ruby's
|
52
|
+
stdlib (WEBrick)
|
53
|
+
- [ ] Configure Sequel's connection pool (i.e. `:max_connections`) to match the
|
54
|
+
application server's thread pool (if any)
|
55
|
+
- [ ] Add caching directives (i.e. `cache_control`, `expires`, `last_modified`,
|
56
|
+
and `etag`) as appropriate
|
57
|
+
|
58
|
+
And probably a whole lot more!
|
data/demo-app/app.rb
CHANGED
@@ -9,6 +9,10 @@ require_relative 'classes/tag'
|
|
9
9
|
|
10
10
|
require 'sinja/helpers/sequel'
|
11
11
|
|
12
|
+
configure :development do
|
13
|
+
set :server_settings, AccessLog: [] # avoid WEBrick double-logging issue
|
14
|
+
end
|
15
|
+
|
12
16
|
configure_jsonapi do |c|
|
13
17
|
Sinja::Helpers::Sequel.config(c)
|
14
18
|
end
|
@@ -19,16 +23,16 @@ helpers Sinja::Helpers::Sequel do
|
|
19
23
|
Author.first_by_email(env['HTTP_X_EMAIL']) if env.key?('HTTP_X_EMAIL')
|
20
24
|
end
|
21
25
|
|
26
|
+
def database
|
27
|
+
DB
|
28
|
+
end
|
29
|
+
|
22
30
|
def role
|
23
31
|
[].tap do |a|
|
24
32
|
a << :logged_in if current_user
|
25
33
|
a << :superuser if current_user&.admin?
|
26
34
|
end
|
27
35
|
end
|
28
|
-
|
29
|
-
def database
|
30
|
-
DB
|
31
|
-
end
|
32
36
|
end
|
33
37
|
|
34
38
|
resource :authors, AuthorController
|
data/demo-app/boot.rb
CHANGED
data/demo-app/classes/author.rb
CHANGED
@@ -4,10 +4,10 @@ require_relative '../database'
|
|
4
4
|
|
5
5
|
DB.create_table?(:authors) do
|
6
6
|
primary_key :id
|
7
|
-
String :email, :
|
7
|
+
String :email, null: false, unique: true
|
8
8
|
String :real_name
|
9
9
|
String :display_name
|
10
|
-
TrueClass :admin, :
|
10
|
+
TrueClass :admin, default: false
|
11
11
|
DateTime :created_at
|
12
12
|
DateTime :updated_at
|
13
13
|
end
|
@@ -17,13 +17,14 @@ class Author < Sequel::Model
|
|
17
17
|
plugin :boolean_readers
|
18
18
|
|
19
19
|
finder def self.by_email(arg)
|
20
|
-
where(:
|
20
|
+
where(email: arg)
|
21
21
|
end
|
22
22
|
|
23
23
|
one_to_many :comments
|
24
24
|
one_to_many :posts
|
25
25
|
end
|
26
26
|
|
27
|
+
# We have to create an admin user here, otherwise we have no way to create one.
|
27
28
|
Author.create(email: 'all@yourbase.com', admin: true)
|
28
29
|
|
29
30
|
class AuthorSerializer < BaseSerializer
|
@@ -40,14 +41,12 @@ AuthorController = proc do
|
|
40
41
|
end
|
41
42
|
|
42
43
|
def role
|
43
|
-
|
44
|
-
|
45
|
-
else
|
46
|
-
super
|
44
|
+
[*super].tap do |a|
|
45
|
+
a << :myself if resource == current_user
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
50
|
-
def
|
49
|
+
def settable_fields
|
51
50
|
%i[email real_name display_name].tap do |a|
|
52
51
|
a << :admin if role?(:superuser)
|
53
52
|
end
|
@@ -56,33 +55,37 @@ AuthorController = proc do
|
|
56
55
|
|
57
56
|
show
|
58
57
|
|
58
|
+
show_many do |ids|
|
59
|
+
Author.where(id: ids.map!(&:to_i)).all
|
60
|
+
end
|
61
|
+
|
59
62
|
index do
|
60
|
-
Author.
|
63
|
+
Author.dataset
|
61
64
|
end
|
62
65
|
|
63
66
|
create do |attr|
|
64
67
|
author = Author.new
|
65
|
-
author.set_fields(attr,
|
68
|
+
author.set_fields(attr, settable_fields)
|
66
69
|
next_pk author.save(validate: false)
|
67
70
|
end
|
68
71
|
|
69
|
-
update(roles: %i[
|
70
|
-
resource.update_fields(attr,
|
72
|
+
update(roles: %i[myself superuser]) do |attr|
|
73
|
+
resource.update_fields(attr, settable_fields, validate: false, missing: :skip)
|
71
74
|
end
|
72
75
|
|
73
|
-
destroy(roles: %i[
|
76
|
+
destroy(roles: %i[myself superuser]) do
|
74
77
|
resource.destroy
|
75
78
|
end
|
76
79
|
|
77
80
|
has_many :comments do
|
78
81
|
fetch(roles: :logged_in) do
|
79
|
-
resource.
|
82
|
+
resource.comments_dataset
|
80
83
|
end
|
81
84
|
end
|
82
85
|
|
83
86
|
has_many :posts do
|
84
87
|
fetch do
|
85
|
-
resource.
|
88
|
+
resource.posts_dataset
|
86
89
|
end
|
87
90
|
end
|
88
91
|
end
|
data/demo-app/classes/comment.rb
CHANGED
@@ -4,9 +4,9 @@ require_relative '../database'
|
|
4
4
|
|
5
5
|
DB.create_table?(:comments) do
|
6
6
|
primary_key :id
|
7
|
-
foreign_key :author_id, :authors, :
|
8
|
-
foreign_key :post_slug, :posts, :on_delete
|
9
|
-
String :body, :
|
7
|
+
foreign_key :author_id, :authors, on_delete: :cascade
|
8
|
+
foreign_key :post_slug, :posts, type: String, on_delete: :cascade, on_update: :cascade
|
9
|
+
String :body, text: true, null: false
|
10
10
|
DateTime :created_at
|
11
11
|
DateTime :updated_at
|
12
12
|
end
|
@@ -37,12 +37,14 @@ CommentController = proc do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def role
|
40
|
-
|
41
|
-
|
42
|
-
else
|
43
|
-
super
|
40
|
+
[*super].tap do |a|
|
41
|
+
a << :owner if resource&.author == current_user
|
44
42
|
end
|
45
43
|
end
|
44
|
+
|
45
|
+
def settable_fields
|
46
|
+
%i[body]
|
47
|
+
end
|
46
48
|
end
|
47
49
|
|
48
50
|
show do |id|
|
@@ -51,13 +53,13 @@ CommentController = proc do
|
|
51
53
|
|
52
54
|
create(roles: :logged_in) do |attr|
|
53
55
|
comment = Comment.new
|
54
|
-
comment.set_fields(attr,
|
56
|
+
comment.set_fields(attr, settable_fields)
|
55
57
|
comment.save(validate: false)
|
56
58
|
next_pk comment
|
57
59
|
end
|
58
60
|
|
59
61
|
update(roles: %i[owner superuser]) do |attr|
|
60
|
-
resource.update_fields(attr,
|
62
|
+
resource.update_fields(attr, settable_fields, validate: false, missing: :skip)
|
61
63
|
end
|
62
64
|
|
63
65
|
destroy(roles: %i[owner superuser]) do
|
data/demo-app/classes/post.rb
CHANGED
@@ -3,10 +3,10 @@ require_relative '../base'
|
|
3
3
|
require_relative '../database'
|
4
4
|
|
5
5
|
DB.create_table?(:posts) do
|
6
|
-
String :slug, :
|
7
|
-
foreign_key :author_id, :authors, :
|
8
|
-
String :title, :
|
9
|
-
String :body, :
|
6
|
+
String :slug, primary_key: true
|
7
|
+
foreign_key :author_id, :authors, on_delete: :cascade
|
8
|
+
String :title, null: false
|
9
|
+
String :body, text: true, null: false
|
10
10
|
DateTime :created_at
|
11
11
|
DateTime :updated_at
|
12
12
|
end
|
@@ -14,11 +14,11 @@ end
|
|
14
14
|
class Post < Sequel::Model
|
15
15
|
plugin :timestamps
|
16
16
|
|
17
|
-
unrestrict_primary_key
|
17
|
+
unrestrict_primary_key # allow client-generated slugs
|
18
18
|
|
19
19
|
many_to_one :author
|
20
20
|
one_to_many :comments
|
21
|
-
many_to_many :tags, :
|
21
|
+
many_to_many :tags, left_key: :post_slug
|
22
22
|
|
23
23
|
def validate
|
24
24
|
super
|
@@ -45,33 +45,38 @@ PostController = proc do
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def role
|
48
|
-
|
49
|
-
|
50
|
-
else
|
51
|
-
super
|
48
|
+
[*super].tap do |a|
|
49
|
+
a << :owner if resource&.author == current_user
|
52
50
|
end
|
53
51
|
end
|
52
|
+
|
53
|
+
def settable_fields
|
54
|
+
%i[title body]
|
55
|
+
end
|
54
56
|
end
|
55
57
|
|
56
58
|
show do |slug|
|
57
59
|
next find(slug), include: %w[author comments tags]
|
58
60
|
end
|
59
61
|
|
62
|
+
show_many do |slugs|
|
63
|
+
next Post.where(slug: slugs.map!(&:to_s)).all, include: %i[author tags]
|
64
|
+
end
|
65
|
+
|
60
66
|
index do
|
61
|
-
|
62
|
-
Post.all
|
67
|
+
Post.dataset
|
63
68
|
end
|
64
69
|
|
65
70
|
create(roles: :logged_in) do |attr, slug|
|
66
71
|
post = Post.new
|
67
|
-
post.set_fields(attr,
|
72
|
+
post.set_fields(attr, settable_fields)
|
68
73
|
post.slug = slug.to_s # set primary key
|
69
74
|
post.save(validate: false)
|
70
75
|
next_pk post
|
71
76
|
end
|
72
77
|
|
73
78
|
update(roles: %i[owner superuser]) do |attr|
|
74
|
-
resource.update_fields(attr,
|
79
|
+
resource.update_fields(attr, settable_fields, validate: false, missing: :skip)
|
75
80
|
end
|
76
81
|
|
77
82
|
destroy(roles: %i[owner superuser]) do
|
@@ -94,13 +99,13 @@ PostController = proc do
|
|
94
99
|
|
95
100
|
has_many :comments do
|
96
101
|
fetch do
|
97
|
-
next resource.
|
102
|
+
next resource.comments_dataset, include: 'author'
|
98
103
|
end
|
99
104
|
end
|
100
105
|
|
101
106
|
has_many :tags do
|
102
107
|
fetch do
|
103
|
-
resource.
|
108
|
+
resource.tags_dataset
|
104
109
|
end
|
105
110
|
|
106
111
|
merge(roles: %i[owner superuser], sideload_on: %i[create update]) do |rios|
|
data/demo-app/classes/tag.rb
CHANGED
@@ -6,18 +6,18 @@ require_relative 'post' # make sure we create the posts table before the join ta
|
|
6
6
|
|
7
7
|
DB.create_table?(:tags) do
|
8
8
|
primary_key :id
|
9
|
-
String :name, :
|
9
|
+
String :name, null: false, unique: true
|
10
10
|
end
|
11
11
|
|
12
12
|
DB.create_table?(:posts_tags) do
|
13
|
-
foreign_key :post_slug, :posts, :null
|
14
|
-
foreign_key :tag_id, :tags, :
|
13
|
+
foreign_key :post_slug, :posts, type: String, null: false, on_delete: :cascade, on_update: :cascade
|
14
|
+
foreign_key :tag_id, :tags, null: false, on_delete: :cascade
|
15
15
|
primary_key [:post_slug, :tag_id]
|
16
16
|
index [:tag_id, :post_slug]
|
17
17
|
end
|
18
18
|
|
19
19
|
class Tag < Sequel::Model
|
20
|
-
many_to_many :posts
|
20
|
+
many_to_many :posts, right_key: :post_slug
|
21
21
|
end
|
22
22
|
|
23
23
|
class TagSerializer < BaseSerializer
|
@@ -31,17 +31,21 @@ TagController = proc do
|
|
31
31
|
def find(id)
|
32
32
|
Tag[id.to_i]
|
33
33
|
end
|
34
|
+
|
35
|
+
def settable_fields
|
36
|
+
%i[name]
|
37
|
+
end
|
34
38
|
end
|
35
39
|
|
36
40
|
show
|
37
41
|
|
38
|
-
index do
|
39
|
-
Tag.
|
42
|
+
index(sort_by: :name, filter_by: :name) do
|
43
|
+
Tag.dataset
|
40
44
|
end
|
41
45
|
|
42
46
|
create(roles: :logged_in) do |attr|
|
43
47
|
tag = Tag.new
|
44
|
-
tag.set_fields(attr,
|
48
|
+
tag.set_fields(attr, settable_fields)
|
45
49
|
tag.save(validate: false)
|
46
50
|
next_pk tag
|
47
51
|
end
|
@@ -52,7 +56,7 @@ TagController = proc do
|
|
52
56
|
|
53
57
|
has_many :posts do
|
54
58
|
fetch do
|
55
|
-
resource.
|
59
|
+
resource.posts_dataset
|
56
60
|
end
|
57
61
|
|
58
62
|
merge(roles: :logged_in) do |rios|
|
data/demo-app/database.rb
CHANGED
@@ -2,11 +2,9 @@
|
|
2
2
|
require 'logger'
|
3
3
|
require_relative 'boot'
|
4
4
|
|
5
|
-
DB =
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
Sequel.sqlite
|
10
|
-
end
|
5
|
+
DB = Sequel.connect ENV.fetch 'DATABASE_URL',
|
6
|
+
defined?(JRUBY_VERSION) ? 'jdbc:sqlite::memory:' : 'sqlite:/'
|
7
|
+
|
8
|
+
DB.extension :pagination
|
11
9
|
|
12
10
|
DB.loggers << Logger.new($stderr) if Sinatra::Base.development?
|
data/lib/sinja/config.rb
CHANGED
@@ -15,7 +15,21 @@ module Sinja
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def deep_freeze(c)
|
18
|
-
c.
|
18
|
+
if c.respond_to?(:default_proc)
|
19
|
+
c.default_proc = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
if c.respond_to?(:values)
|
23
|
+
c.values.each do |i|
|
24
|
+
if Hash === i
|
25
|
+
deep_freeze(i)
|
26
|
+
else
|
27
|
+
i.freeze
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
c.freeze
|
19
33
|
end
|
20
34
|
end
|
21
35
|
|
@@ -33,41 +47,54 @@ module Sinja
|
|
33
47
|
}.freeze
|
34
48
|
|
35
49
|
attr_reader \
|
50
|
+
:query_params,
|
36
51
|
:error_logger,
|
37
|
-
:
|
38
|
-
:default_has_many_roles,
|
39
|
-
:default_has_one_roles,
|
40
|
-
:resource_roles,
|
41
|
-
:resource_sideload,
|
52
|
+
:resource_config,
|
42
53
|
:conflict_exceptions,
|
43
54
|
:not_found_exceptions,
|
44
55
|
:validation_exceptions,
|
45
56
|
:validation_formatter,
|
57
|
+
:page_using,
|
46
58
|
:serializer_opts
|
47
59
|
|
48
60
|
def initialize
|
49
|
-
@
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
61
|
+
@query_params = {
|
62
|
+
:fields=>{}, # passthru to JAS
|
63
|
+
:include=>[], # passthru to JAS
|
64
|
+
:filter=>{},
|
65
|
+
:page=>{},
|
66
|
+
:sort=>[],
|
67
|
+
:capture=>nil
|
68
|
+
}
|
69
|
+
|
70
|
+
@error_logger = ->(h) { logger.error('sinja') { h } }
|
71
|
+
|
72
|
+
@default_roles = {
|
73
|
+
:resource=>RolesConfig.new(%i[show show_many index create update destroy]),
|
74
|
+
:has_many=>RolesConfig.new(%i[fetch merge subtract clear]),
|
75
|
+
:has_one=>RolesConfig.new(%i[pluck graft prune])
|
76
|
+
}
|
77
|
+
|
78
|
+
action_proc = proc { |type, hash, action| hash[action] = {
|
79
|
+
:roles=>@default_roles[type][action].dup,
|
80
|
+
:sideload_on=>Set.new,
|
81
|
+
:filter_by=>Set.new,
|
82
|
+
:sort_by=>Set.new
|
83
|
+
}}.curry
|
84
|
+
|
85
|
+
@resource_config = Hash.new { |h, k| h[k] = {
|
86
|
+
:resource=>Hash.new(&action_proc[:resource]),
|
87
|
+
:has_many=>Hash.new { |rh, rk| rh[rk] = Hash.new(&action_proc[:has_many]) },
|
88
|
+
:has_one=>Hash.new { |rh, rk| rh[rk] = Hash.new(&action_proc[:has_one]) }
|
59
89
|
}}
|
60
90
|
|
61
|
-
@resource_sideload = Hash.new do |h, k|
|
62
|
-
h[k] = SideloadConfig.new(Resource::SIDELOAD_ACTIONS)
|
63
|
-
end
|
64
|
-
|
65
91
|
@conflict_exceptions = Set.new
|
66
92
|
@not_found_exceptions = Set.new
|
67
93
|
@validation_exceptions = Set.new
|
68
94
|
@validation_formatter = ->{ Array.new }
|
69
95
|
|
70
96
|
@opts = deep_copy(DEFAULT_OPTS)
|
97
|
+
@page_using = Hash.new
|
71
98
|
@serializer_opts = deep_copy(DEFAULT_SERIALIZER_OPTS)
|
72
99
|
end
|
73
100
|
|
@@ -82,15 +109,15 @@ module Sinja
|
|
82
109
|
end
|
83
110
|
|
84
111
|
def conflict_exceptions=(e=[])
|
85
|
-
@conflict_exceptions.replace(
|
112
|
+
@conflict_exceptions.replace([*e])
|
86
113
|
end
|
87
114
|
|
88
115
|
def not_found_exceptions=(e=[])
|
89
|
-
@not_found_exceptions.replace(
|
116
|
+
@not_found_exceptions.replace([*e])
|
90
117
|
end
|
91
118
|
|
92
119
|
def validation_exceptions=(e=[])
|
93
|
-
@validation_exceptions.replace(
|
120
|
+
@validation_exceptions.replace([*e])
|
94
121
|
end
|
95
122
|
|
96
123
|
def validation_formatter=(f)
|
@@ -103,12 +130,28 @@ module Sinja
|
|
103
130
|
@validation_formatter = f
|
104
131
|
end
|
105
132
|
|
106
|
-
|
107
|
-
|
108
|
-
|
133
|
+
def default_roles
|
134
|
+
@default_roles[:resource]
|
135
|
+
end
|
109
136
|
|
110
|
-
def
|
111
|
-
@
|
137
|
+
def default_roles=(other={})
|
138
|
+
@default_roles[:resource].merge!(other)
|
139
|
+
end
|
140
|
+
|
141
|
+
def default_has_many_roles
|
142
|
+
@default_roles[:has_many]
|
143
|
+
end
|
144
|
+
|
145
|
+
def default_has_many_roles=(other={})
|
146
|
+
@default_roles[:has_many].merge!(other)
|
147
|
+
end
|
148
|
+
|
149
|
+
def default_has_one_roles
|
150
|
+
@default_roles[:has_one]
|
151
|
+
end
|
152
|
+
|
153
|
+
def default_has_one_roles=(other={})
|
154
|
+
@default_roles[:has_one].merge!(other)
|
112
155
|
end
|
113
156
|
|
114
157
|
DEFAULT_OPTS.keys.each do |k|
|
@@ -116,34 +159,29 @@ module Sinja
|
|
116
159
|
define_method("#{k}=") { |v| @opts[k] = v }
|
117
160
|
end
|
118
161
|
|
162
|
+
def page_using=(p={})
|
163
|
+
@page_using.replace(p)
|
164
|
+
end
|
165
|
+
|
166
|
+
def serializer_opts=(h={})
|
167
|
+
@serializer_opts.replace(deep_copy(DEFAULT_SERIALIZER_OPTS).merge!(h))
|
168
|
+
end
|
169
|
+
|
119
170
|
def freeze
|
171
|
+
@query_params.freeze
|
120
172
|
@error_logger.freeze
|
121
173
|
|
122
|
-
@default_roles
|
123
|
-
@
|
124
|
-
@default_has_one_roles.freeze
|
125
|
-
|
126
|
-
@resource_roles.default_proc = nil
|
127
|
-
@resource_roles.values.each do |h|
|
128
|
-
h[:resource].freeze
|
129
|
-
h[:has_many].default_proc = nil
|
130
|
-
deep_freeze(h[:has_many])
|
131
|
-
h[:has_one].default_proc = nil
|
132
|
-
deep_freeze(h[:has_one])
|
133
|
-
end
|
134
|
-
deep_freeze(@resource_roles)
|
135
|
-
|
136
|
-
@resource_sideload.default_proc = nil
|
137
|
-
deep_freeze(@resource_sideload)
|
174
|
+
deep_freeze(@default_roles)
|
175
|
+
deep_freeze(@resource_config)
|
138
176
|
|
139
177
|
@conflict_exceptions.freeze
|
140
178
|
@not_found_exceptions.freeze
|
141
179
|
@validation_exceptions.freeze
|
142
180
|
@validation_formatter.freeze
|
143
181
|
|
144
|
-
deep_freeze(@serializer_opts)
|
145
|
-
|
146
182
|
@opts.freeze
|
183
|
+
@page_using.freeze
|
184
|
+
deep_freeze(@serializer_opts)
|
147
185
|
|
148
186
|
super
|
149
187
|
end
|
@@ -173,7 +211,7 @@ module Sinja
|
|
173
211
|
h.each do |action, roles|
|
174
212
|
abort "Unknown or invalid action helper `#{action}' in configuration" \
|
175
213
|
unless @data.key?(action)
|
176
|
-
@data[action].replace(
|
214
|
+
@data[action].replace([*roles])
|
177
215
|
end
|
178
216
|
@data
|
179
217
|
end
|
@@ -188,33 +226,4 @@ module Sinja
|
|
188
226
|
super
|
189
227
|
end
|
190
228
|
end
|
191
|
-
|
192
|
-
class SideloadConfig
|
193
|
-
include ConfigUtils
|
194
|
-
extend Forwardable
|
195
|
-
|
196
|
-
def initialize(actions=[])
|
197
|
-
@data = actions.map { |child| [child, Set.new] }.to_h
|
198
|
-
end
|
199
|
-
|
200
|
-
def_delegators :@data, :[], :dig
|
201
|
-
|
202
|
-
def ==(other)
|
203
|
-
@data == other.instance_variable_get(:@data)
|
204
|
-
end
|
205
|
-
|
206
|
-
def merge!(h={})
|
207
|
-
h.each do |child, parents|
|
208
|
-
abort "Unknown or invalid action helper `#{child}' in configuration" \
|
209
|
-
unless @data.key?(child)
|
210
|
-
@data[child].replace(Set[*parents])
|
211
|
-
end
|
212
|
-
@data
|
213
|
-
end
|
214
|
-
|
215
|
-
def freeze
|
216
|
-
deep_freeze(@data)
|
217
|
-
super
|
218
|
-
end
|
219
|
-
end
|
220
229
|
end
|
@@ -19,9 +19,11 @@ module Sinja
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def dispatch_relationship_requests!(id, methods: {}, **opts)
|
22
|
-
data.fetch(:relationships, {}).each do |
|
23
|
-
|
24
|
-
code, _, *json = dispatch_relationship_request
|
22
|
+
data.fetch(:relationships, {}).each do |rel, body|
|
23
|
+
rel_type = settings._resource_config[:has_one].key?(rel) ? :has_one : :has_many
|
24
|
+
code, _, *json = dispatch_relationship_request id, rel,
|
25
|
+
opts.merge(:body=>body, :method=>methods.fetch(rel_type, :patch))
|
26
|
+
|
25
27
|
# TODO: Gather responses and report all errors instead of only first?
|
26
28
|
# `halt' was called (instead of raise); rethrow it as best as possible
|
27
29
|
raise SideloadError.new(code, json) unless (200...300).cover?(code)
|