sinja 0.2.0.beta2 → 1.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|