sinja 0.2.0.beta2 → 1.0.0.pre1
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 +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +12 -2
- data/Gemfile +2 -0
- data/README.md +526 -99
- data/Rakefile +7 -3
- data/demo-app/Gemfile +3 -0
- data/demo-app/app.rb +39 -0
- data/demo-app/base.rb +9 -0
- data/demo-app/boot.rb +4 -0
- data/demo-app/classes/author.rb +88 -0
- data/demo-app/classes/comment.rb +91 -0
- data/demo-app/classes/post.rb +118 -0
- data/demo-app/classes/tag.rb +70 -0
- data/demo-app/database.rb +12 -0
- data/demo-app/test.rb +17 -0
- data/lib/sinja.rb +157 -29
- data/lib/sinja/config.rb +123 -29
- data/lib/sinja/errors.rb +69 -0
- data/lib/sinja/{relationship_routes → extensions}/sequel.rb +1 -1
- data/lib/sinja/helpers/nested.rb +10 -0
- data/lib/sinja/helpers/relationships.rb +16 -7
- data/lib/sinja/helpers/sequel.rb +16 -11
- data/lib/sinja/helpers/serializers.rb +127 -53
- data/lib/sinja/method_override.rb +15 -0
- data/lib/sinja/relationship_routes/has_many.rb +14 -1
- data/lib/sinja/relationship_routes/has_one.rb +14 -1
- data/lib/sinja/resource.rb +40 -59
- data/lib/sinja/resource_routes.rb +46 -26
- data/lib/sinja/version.rb +2 -2
- data/sinja.gemspec +18 -7
- metadata +137 -25
- data/lib/role_list.rb +0 -8
data/Rakefile
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'bundler/gem_tasks'
|
2
|
-
require '
|
3
|
+
require 'rake/testtask'
|
3
4
|
|
4
|
-
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.test_files = FileList[File.expand_path('test/**/*_test.rb', __dir__)]
|
7
|
+
t.warning = false
|
8
|
+
end
|
5
9
|
|
6
|
-
task :
|
10
|
+
task default: :test
|
data/demo-app/Gemfile
ADDED
data/demo-app/app.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sinatra'
|
3
|
+
require 'sinatra/jsonapi'
|
4
|
+
|
5
|
+
require_relative 'classes/author'
|
6
|
+
require_relative 'classes/comment'
|
7
|
+
require_relative 'classes/post'
|
8
|
+
require_relative 'classes/tag'
|
9
|
+
|
10
|
+
require 'sinja/helpers/sequel'
|
11
|
+
|
12
|
+
configure_jsonapi do |c|
|
13
|
+
Sinja::Helpers::Sequel.config(c)
|
14
|
+
end
|
15
|
+
|
16
|
+
helpers Sinja::Helpers::Sequel do
|
17
|
+
def current_user
|
18
|
+
# TESTING/DEMO PURPOSES ONLY -- DO NOT DO THIS IN PRODUCTION
|
19
|
+
Author.first_by_email(env['HTTP_X_EMAIL']) if env.key?('HTTP_X_EMAIL')
|
20
|
+
end
|
21
|
+
|
22
|
+
def role
|
23
|
+
[].tap do |a|
|
24
|
+
a << :logged_in if current_user
|
25
|
+
a << :superuser if current_user&.admin?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def database
|
30
|
+
DB
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
resource :authors, AuthorController
|
35
|
+
resource :comments, CommentController
|
36
|
+
resource :posts, PostController
|
37
|
+
resource :tags, TagController
|
38
|
+
|
39
|
+
freeze_jsonapi
|
data/demo-app/base.rb
ADDED
data/demo-app/boot.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative '../base'
|
3
|
+
require_relative '../database'
|
4
|
+
|
5
|
+
DB.create_table?(:authors) do
|
6
|
+
primary_key :id
|
7
|
+
String :email, :null=>false, :unique=>true
|
8
|
+
String :real_name
|
9
|
+
String :display_name
|
10
|
+
TrueClass :admin, :default=>false
|
11
|
+
DateTime :created_at
|
12
|
+
DateTime :updated_at
|
13
|
+
end
|
14
|
+
|
15
|
+
class Author < Sequel::Model
|
16
|
+
plugin :timestamps
|
17
|
+
plugin :boolean_readers
|
18
|
+
|
19
|
+
finder def self.by_email(arg)
|
20
|
+
where(:email=>arg)
|
21
|
+
end
|
22
|
+
|
23
|
+
one_to_many :comments
|
24
|
+
one_to_many :posts
|
25
|
+
end
|
26
|
+
|
27
|
+
Author.create(email: 'all@yourbase.com', admin: true)
|
28
|
+
|
29
|
+
class AuthorSerializer < BaseSerializer
|
30
|
+
attribute(:display_name) { object.display_name || 'Anonymous Coward' }
|
31
|
+
|
32
|
+
has_many :comments
|
33
|
+
has_many :posts
|
34
|
+
end
|
35
|
+
|
36
|
+
AuthorController = proc do
|
37
|
+
helpers do
|
38
|
+
def find(id)
|
39
|
+
Author[id.to_i]
|
40
|
+
end
|
41
|
+
|
42
|
+
def role
|
43
|
+
if resource == current_user
|
44
|
+
super.push(:self)
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def fields
|
51
|
+
%i[email real_name display_name].tap do |a|
|
52
|
+
a << :admin if role?(:superuser)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
show
|
58
|
+
|
59
|
+
index do
|
60
|
+
Author.all
|
61
|
+
end
|
62
|
+
|
63
|
+
create do |attr|
|
64
|
+
author = Author.new
|
65
|
+
author.set_fields(attr, fields)
|
66
|
+
next_pk author.save(validate: false)
|
67
|
+
end
|
68
|
+
|
69
|
+
update(roles: %i[self superuser]) do |attr|
|
70
|
+
resource.update_fields(attr, fields, validate: false, missing: :skip)
|
71
|
+
end
|
72
|
+
|
73
|
+
destroy(roles: %i[self superuser]) do
|
74
|
+
resource.destroy
|
75
|
+
end
|
76
|
+
|
77
|
+
has_many :comments do
|
78
|
+
fetch(roles: :logged_in) do
|
79
|
+
resource.comments
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
has_many :posts do
|
84
|
+
fetch do
|
85
|
+
resource.posts
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative '../base'
|
3
|
+
require_relative '../database'
|
4
|
+
|
5
|
+
DB.create_table?(:comments) do
|
6
|
+
primary_key :id
|
7
|
+
foreign_key :author_id, :authors, :on_delete=>:cascade
|
8
|
+
foreign_key :post_slug, :posts, :on_delete=>:cascade, :type=>String
|
9
|
+
String :body, :text=>true, :null=>false
|
10
|
+
DateTime :created_at
|
11
|
+
DateTime :updated_at
|
12
|
+
end
|
13
|
+
|
14
|
+
class Comment < Sequel::Model
|
15
|
+
plugin :timestamps
|
16
|
+
|
17
|
+
many_to_one :author
|
18
|
+
many_to_one :post
|
19
|
+
|
20
|
+
def validate
|
21
|
+
super
|
22
|
+
validates_not_null [:author, :post]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class CommentSerializer < BaseSerializer
|
27
|
+
attribute :body
|
28
|
+
|
29
|
+
has_one :author
|
30
|
+
has_one :post
|
31
|
+
end
|
32
|
+
|
33
|
+
CommentController = proc do
|
34
|
+
helpers do
|
35
|
+
def find(id)
|
36
|
+
Comment[id.to_i]
|
37
|
+
end
|
38
|
+
|
39
|
+
def role
|
40
|
+
if resource&.author == current_user
|
41
|
+
super.push(:owner)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
show do |id|
|
49
|
+
next find(id), include: 'author'
|
50
|
+
end
|
51
|
+
|
52
|
+
create(roles: :logged_in) do |attr|
|
53
|
+
comment = Comment.new
|
54
|
+
comment.set_fields(attr, %i[body])
|
55
|
+
comment.save(validate: false)
|
56
|
+
next_pk comment
|
57
|
+
end
|
58
|
+
|
59
|
+
update(roles: %i[owner superuser]) do |attr|
|
60
|
+
resource.update_fields(attr, %i[body], validate: false, missing: :skip)
|
61
|
+
end
|
62
|
+
|
63
|
+
destroy(roles: %i[owner superuser]) do
|
64
|
+
resource.destroy
|
65
|
+
end
|
66
|
+
|
67
|
+
has_one :post do
|
68
|
+
pluck do
|
69
|
+
resource.post
|
70
|
+
end
|
71
|
+
|
72
|
+
graft(roles: :superuser, sideload_on: :create) do |rio|
|
73
|
+
resource.post = Post.with_pk!(rio[:id].to_i)
|
74
|
+
resource.save_changes(validate: !sideloaded?)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
has_one :author do
|
79
|
+
pluck do
|
80
|
+
resource.author
|
81
|
+
end
|
82
|
+
|
83
|
+
graft(roles: :superuser, sideload_on: :create) do |rio|
|
84
|
+
halt 403, 'You may only assign yourself as comment author!' \
|
85
|
+
unless role?(:superuser) || rio[:id].to_i == current_user.id
|
86
|
+
|
87
|
+
resource.author = Author.with_pk!(rio[:id].to_i)
|
88
|
+
resource.save_changes(validate: !sideloaded?)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative '../base'
|
3
|
+
require_relative '../database'
|
4
|
+
|
5
|
+
DB.create_table?(:posts) do
|
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
|
+
DateTime :created_at
|
11
|
+
DateTime :updated_at
|
12
|
+
end
|
13
|
+
|
14
|
+
class Post < Sequel::Model
|
15
|
+
plugin :timestamps
|
16
|
+
|
17
|
+
unrestrict_primary_key
|
18
|
+
|
19
|
+
many_to_one :author
|
20
|
+
one_to_many :comments
|
21
|
+
many_to_many :tags, :left_key=>:post_slug
|
22
|
+
|
23
|
+
def validate
|
24
|
+
super
|
25
|
+
validates_not_null :author
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class PostSerializer < BaseSerializer
|
30
|
+
def id
|
31
|
+
object.slug
|
32
|
+
end
|
33
|
+
|
34
|
+
attributes :title, :body
|
35
|
+
|
36
|
+
has_one :author
|
37
|
+
has_many :comments
|
38
|
+
has_many :tags
|
39
|
+
end
|
40
|
+
|
41
|
+
PostController = proc do
|
42
|
+
helpers do
|
43
|
+
def find(slug)
|
44
|
+
Post[slug.to_s]
|
45
|
+
end
|
46
|
+
|
47
|
+
def role
|
48
|
+
if resource&.author == current_user
|
49
|
+
super.push(:owner)
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
show do |slug|
|
57
|
+
next find(slug), include: %w[author comments tags]
|
58
|
+
end
|
59
|
+
|
60
|
+
index do
|
61
|
+
# TODO: Filter/sort by created_at and/or updated_at?
|
62
|
+
Post.all
|
63
|
+
end
|
64
|
+
|
65
|
+
create(roles: :logged_in) do |attr, slug|
|
66
|
+
post = Post.new
|
67
|
+
post.set_fields(attr, %i[title body])
|
68
|
+
post.slug = slug.to_s # set primary key
|
69
|
+
post.save(validate: false)
|
70
|
+
next_pk post
|
71
|
+
end
|
72
|
+
|
73
|
+
update(roles: %i[owner superuser]) do |attr|
|
74
|
+
resource.update_fields(attr, %i[title body], validate: false, missing: :skip)
|
75
|
+
end
|
76
|
+
|
77
|
+
destroy(roles: %i[owner superuser]) do
|
78
|
+
resource.destroy
|
79
|
+
end
|
80
|
+
|
81
|
+
has_one :author do
|
82
|
+
pluck do
|
83
|
+
resource.author
|
84
|
+
end
|
85
|
+
|
86
|
+
graft(roles: :superuser, sideload_on: :create) do |rio|
|
87
|
+
halt 403, 'You may only assign yourself as post author!' \
|
88
|
+
unless role?(:superuser) || rio[:id].to_i == current_user.id
|
89
|
+
|
90
|
+
resource.author = Author.with_pk!(rio[:id].to_i)
|
91
|
+
resource.save_changes(validate: !sideloaded?)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
has_many :comments do
|
96
|
+
fetch do
|
97
|
+
next resource.comments, include: 'author'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
has_many :tags do
|
102
|
+
fetch do
|
103
|
+
resource.tags
|
104
|
+
end
|
105
|
+
|
106
|
+
merge(roles: %i[owner superuser], sideload_on: %i[create update]) do |rios|
|
107
|
+
add_missing(:tags, rios)
|
108
|
+
end
|
109
|
+
|
110
|
+
subtract(roles: %i[owner superuser]) do |rios|
|
111
|
+
remove_present(:tags, rios)
|
112
|
+
end
|
113
|
+
|
114
|
+
clear(roles: %i[owner superuser], sideload_on: :update) do
|
115
|
+
resource.remove_all_tags
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative '../base'
|
3
|
+
require_relative '../database'
|
4
|
+
|
5
|
+
require_relative 'post' # make sure we create the posts table before the join table
|
6
|
+
|
7
|
+
DB.create_table?(:tags) do
|
8
|
+
primary_key :id
|
9
|
+
String :name, :null=>false, :unique=>true
|
10
|
+
end
|
11
|
+
|
12
|
+
DB.create_table?(:posts_tags) do
|
13
|
+
foreign_key :post_slug, :posts, :null=>false, :on_delete=>:cascade, :type=>String
|
14
|
+
foreign_key :tag_id, :tags, :null=>false, :on_delete=>:cascade
|
15
|
+
primary_key [:post_slug, :tag_id]
|
16
|
+
index [:tag_id, :post_slug]
|
17
|
+
end
|
18
|
+
|
19
|
+
class Tag < Sequel::Model
|
20
|
+
many_to_many :posts
|
21
|
+
end
|
22
|
+
|
23
|
+
class TagSerializer < BaseSerializer
|
24
|
+
attribute :name
|
25
|
+
|
26
|
+
has_many :posts
|
27
|
+
end
|
28
|
+
|
29
|
+
TagController = proc do
|
30
|
+
helpers do
|
31
|
+
def find(id)
|
32
|
+
Tag[id.to_i]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
show
|
37
|
+
|
38
|
+
index do
|
39
|
+
Tag.all
|
40
|
+
end
|
41
|
+
|
42
|
+
create(roles: :logged_in) do |attr|
|
43
|
+
tag = Tag.new
|
44
|
+
tag.set_fields(attr, %i[name])
|
45
|
+
tag.save(validate: false)
|
46
|
+
next_pk tag
|
47
|
+
end
|
48
|
+
|
49
|
+
destroy(roles: :superuser) do
|
50
|
+
resource.destroy
|
51
|
+
end
|
52
|
+
|
53
|
+
has_many :posts do
|
54
|
+
fetch do
|
55
|
+
resource.posts
|
56
|
+
end
|
57
|
+
|
58
|
+
merge(roles: :logged_in) do |rios|
|
59
|
+
add_missing(:posts, rios) do |post|
|
60
|
+
role?(:superuser) || post.author == current_user
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
subtract(roles: :logged_in) do |rios|
|
65
|
+
remove_present(:posts, rios) do |post|
|
66
|
+
role?(:superuser) || post.author == current_user
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|