sinja 1.1.0.pre4 → 1.2.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/README.md +96 -29
  4. data/demo-app/.dockerignore +1 -0
  5. data/demo-app/Dockerfile +26 -0
  6. data/demo-app/Gemfile +12 -1
  7. data/demo-app/README.md +39 -24
  8. data/demo-app/Rakefile +36 -0
  9. data/demo-app/app.rb +3 -10
  10. data/demo-app/boot.rb +0 -4
  11. data/demo-app/classes/author.rb +3 -4
  12. data/demo-app/{base.rb → classes/base.rb} +2 -1
  13. data/demo-app/classes/comment.rb +1 -2
  14. data/demo-app/classes/post.rb +17 -10
  15. data/demo-app/classes/tag.rb +7 -3
  16. data/extensions/sequel/Gemfile +4 -0
  17. data/extensions/sequel/LICENSE.txt +21 -0
  18. data/extensions/sequel/README.md +274 -0
  19. data/extensions/sequel/Rakefile +10 -0
  20. data/extensions/sequel/bin/console +14 -0
  21. data/extensions/sequel/bin/setup +8 -0
  22. data/extensions/sequel/lib/sinatra/jsonapi/sequel.rb +7 -0
  23. data/extensions/sequel/lib/sinja-sequel.rb +2 -0
  24. data/extensions/sequel/lib/sinja/sequel.rb +110 -0
  25. data/extensions/sequel/lib/sinja/sequel/core.rb +72 -0
  26. data/extensions/sequel/lib/sinja/sequel/helpers.rb +78 -0
  27. data/extensions/sequel/lib/sinja/sequel/version.rb +6 -0
  28. data/extensions/sequel/sinja-sequel.gemspec +29 -0
  29. data/extensions/sequel/test/test_helper.rb +3 -0
  30. data/lib/sinja.rb +40 -18
  31. data/lib/sinja/config.rb +4 -3
  32. data/lib/sinja/helpers/serializers.rb +2 -1
  33. data/lib/sinja/relationship_routes/has_many.rb +8 -9
  34. data/lib/sinja/relationship_routes/has_one.rb +1 -1
  35. data/lib/sinja/resource.rb +2 -1
  36. data/lib/sinja/resource_routes.rb +2 -2
  37. data/lib/sinja/version.rb +1 -1
  38. data/sinja.gemspec +5 -7
  39. metadata +39 -11
  40. data/.rspec +0 -2
  41. data/lib/sinja/extensions/sequel.rb +0 -53
  42. data/lib/sinja/helpers/sequel.rb +0 -101
data/demo-app/boot.rb CHANGED
@@ -1,6 +1,2 @@
1
1
  # frozen_string_literal: true
2
2
  require 'bundler/setup'
3
-
4
- Bundler.require :default
5
- Bundler.require Sinatra::Base.environment
6
- Bundler.require :development if Sinatra::Base.test?
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require_relative '../base'
3
- require_relative '../database'
2
+ require_relative 'base'
4
3
 
5
4
  DB.create_table?(:authors) do
6
5
  primary_key :id
@@ -13,8 +12,8 @@ DB.create_table?(:authors) do
13
12
  end
14
13
 
15
14
  class Author < Sequel::Model
16
- plugin :timestamps
17
15
  plugin :boolean_readers
16
+ plugin :timestamps
18
17
 
19
18
  finder def self.by_email(arg)
20
19
  where(email: arg)
@@ -25,7 +24,7 @@ class Author < Sequel::Model
25
24
  end
26
25
 
27
26
  # We have to create an admin user here, otherwise we have no way to create one.
28
- Author.create(email: 'all@yourbase.com', admin: true)
27
+ Author.create(email: 'all@yourbase.com', admin: true) if Author.where(admin: true).empty?
29
28
 
30
29
  class AuthorSerializer < BaseSerializer
31
30
  attribute(:display_name) { object.display_name || 'Anonymous Coward' }
@@ -1,5 +1,6 @@
1
1
  # require_string_literal: true
2
- require_relative 'boot'
2
+ require_relative '../boot'
3
+ require_relative '../database'
3
4
 
4
5
  class BaseSerializer
5
6
  include JSONAPI::Serializer
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require_relative '../base'
3
- require_relative '../database'
2
+ require_relative 'base'
4
3
 
5
4
  DB.create_table?(:comments) do
6
5
  primary_key :id
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require_relative '../base'
3
- require_relative '../database'
2
+ require_relative 'base'
4
3
 
5
4
  DB.create_table?(:posts) do
6
5
  String :slug, primary_key: true
@@ -13,9 +12,14 @@ end
13
12
 
14
13
  class Post < Sequel::Model
15
14
  plugin :timestamps
15
+ plugin :update_primary_key
16
16
 
17
17
  unrestrict_primary_key # allow client-generated slugs
18
18
 
19
+ # jdbc-sqlite3 reports unexpected record counts with cascading updates, which
20
+ # breaks Sequel (https://github.com/jeremyevans/sequel/issues/1275)
21
+ self.require_modification = !defined?(JRUBY_VERSION)
22
+
19
23
  many_to_one :author
20
24
  one_to_many :comments
21
25
  many_to_many :tags, left_key: :post_slug
@@ -51,7 +55,7 @@ PostController = proc do
51
55
  end
52
56
 
53
57
  def settable_fields
54
- %i[title body]
58
+ %i[slug title body]
55
59
  end
56
60
  end
57
61
 
@@ -69,8 +73,7 @@ PostController = proc do
69
73
 
70
74
  create(roles: :logged_in) do |attr, slug|
71
75
  post = Post.new
72
- post.set_fields(attr, settable_fields)
73
- post.slug = slug.to_s # set primary key
76
+ post.set_fields(attr.merge(slug: slug), settable_fields)
74
77
  post.save(validate: false)
75
78
  next_pk post
76
79
  end
@@ -108,16 +111,20 @@ PostController = proc do
108
111
  resource.tags_dataset
109
112
  end
110
113
 
111
- merge(roles: %i[owner superuser], sideload_on: %i[create update]) do |rios|
114
+ clear(roles: %i[owner superuser], sideload_on: :update) do
115
+ resource.remove_all_tags
116
+ end
117
+
118
+ replace(roles: %i[owner superuser], sideload_on: :update) do |rios|
119
+ add_remove(:tags, rios)
120
+ end
121
+
122
+ merge(roles: %i[owner superuser], sideload_on: :create) do |rios|
112
123
  add_missing(:tags, rios)
113
124
  end
114
125
 
115
126
  subtract(roles: %i[owner superuser]) do |rios|
116
127
  remove_present(:tags, rios)
117
128
  end
118
-
119
- clear(roles: %i[owner superuser], sideload_on: :update) do
120
- resource.remove_all_tags
121
- end
122
129
  end
123
130
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require_relative '../base'
3
- require_relative '../database'
4
-
2
+ require_relative 'base'
5
3
  require_relative 'post' # make sure we create the posts table before the join table
6
4
 
7
5
  DB.create_table?(:tags) do
@@ -59,6 +57,12 @@ TagController = proc do
59
57
  resource.posts_dataset
60
58
  end
61
59
 
60
+ replace(roles: :logged_in) do |rios|
61
+ add_remove(:posts, rios) do |post|
62
+ role?(:superuser) || post.author == current_user
63
+ end
64
+ end
65
+
62
66
  merge(roles: :logged_in) do |rios|
63
67
  add_missing(:posts, rios) do |post|
64
68
  role?(:superuser) || post.author == current_user
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ gem 'sinja', :require=>false, :path=>'../..'
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Mike Pastore
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,274 @@
1
+ # Sinja::Sequel
2
+
3
+ <!--
4
+ Title: Sinja::Sequel
5
+ Description: Sequel-specific Helpers and DSL for Sinja
6
+ Author: Mike Pastore
7
+ Keywords: Ruby, Sinatra, Sinatra::JSONAPI, Sinja, Sequel
8
+ -->
9
+
10
+ [![Gem Version](https://badge.fury.io/rb/sinja-sequel.svg)](https://badge.fury.io/rb/sinja-sequel)
11
+
12
+ Sinja::Sequel configures your [Sinja][1] application to work with [Sequel][2]
13
+ out of the box, and provides additional helpers to greatly simplify the process
14
+ of writing the more complex action helpers (specifically `replace`, `merge`,
15
+ and `subtract`). An optional extension enhances Sinja's DSL to generate basic
16
+ action helpers that can be overridden, customized, or removed.
17
+
18
+ The core configuration and helpers are in pretty good shape (Sinja uses them in
19
+ its [demo app][3] and test suite), but the extension could use some fleshing
20
+ out. Testers and community contributions welcome!
21
+
22
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
23
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
24
+
25
+
26
+ - [Installation](#installation)
27
+ - [Usage](#usage)
28
+ - [Core](#core)
29
+ - [Helpers](#helpers)
30
+ - [`next_pk`](#next_pk)
31
+ - [`add_missing`](#add_missing)
32
+ - [`remove_present`](#remove_present)
33
+ - [`add_remove`](#add_remove)
34
+ - [Extension](#extension)
35
+ - [Development](#development)
36
+ - [Contributing](#contributing)
37
+ - [License](#license)
38
+
39
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
40
+
41
+ ## Installation
42
+
43
+ Add this line to your application's Gemfile:
44
+
45
+ ```ruby
46
+ gem 'sinja-sequel'
47
+ ```
48
+
49
+ And then execute:
50
+
51
+ ```sh
52
+ $ bundle
53
+ ```
54
+
55
+ Or install it yourself as:
56
+
57
+ ```sh
58
+ $ gem install sinja-sequel
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ Always return Sequel datasets (instead of arrays of objects) from your `index`
64
+ (e.g. `Foo.dataset`) and `fetch` (e.g. `resource.bars_dataset`) action
65
+ helpers. The `finalize` helper, described below, will ensure they are
66
+ "rasterized" before being passed to [JSONAPI::Serializers][5].
67
+
68
+ You'll want to enable Sequel's `:tactical_eager_loading` plugin for the best
69
+ performance with JSONAPI::Serializers. I've [seen][6] it reduce complex
70
+ serializations by a factor of 100 (i.e. quite literally one query instead of
71
+ 100).
72
+
73
+ If you want to use client-generated IDs, enable the `:update_primary_key`
74
+ plugin on the model and call `unrestrict_primary_key` in the model definition
75
+ to allow mass assignment (e.g. with `Sequel::Model#set_fields`).
76
+
77
+ If your model has foreign keys and you want to enforce a non-nullable
78
+ constraint at the application level, consider enabling the
79
+ `:validation_helpers` plugin on the model and using `validates_not_null` in
80
+ conjuction with the `validate!` helper described below:
81
+
82
+ ```ruby
83
+ class Bar < Sequel::Model
84
+ plugin :validation_helpers
85
+
86
+ def validate
87
+ super
88
+ validates_not_null :foo
89
+ end
90
+ end
91
+ ```
92
+
93
+ See "Avoding Null Foreign Keys" in the [Sinja][1] documentation for more
94
+ information.
95
+
96
+ Finally, enable the `:pagination` extension on your connection (before
97
+ prepending Core) to enable pagination!
98
+
99
+ ### Core
100
+
101
+ Prepend [Sinja::Sequel::Core](/extensions/sequel/lib/sinja/sequel/core.rb)
102
+ after registering Sinja:
103
+
104
+ ```ruby
105
+ require 'sinja'
106
+ require 'sinja/sequel/core'
107
+
108
+ class MyApp < Sinatra::Base
109
+ register Sinja
110
+
111
+ helpers do
112
+ prepend Sinja::Sequel::Core
113
+ end
114
+ end
115
+ ```
116
+
117
+ Note that you must use `prepend` (instead of including Sinja::Sequel::Core like
118
+ a normal module of Sinatra helpers) in order to ensure that the included
119
+ methods take precedence over Sinja's method stubs (e.g. `transaction`).
120
+ [This][4] will hopefully be fixed in a future version of Sinatra.
121
+
122
+ Prepending Core does the following to your application:
123
+
124
+ * Configures `conflict_`, `not_found_`, and `validation_exceptions`, and
125
+ `validation_formatter`.
126
+ * Defines a `database` helper that delegates to `Sequel::Model.db`.
127
+ * Defines a `transaction` helper that delegates to `database.transaction`.
128
+ * Defines a `validate!` helper that raises an error if `resource` is invalid
129
+ after a `create` or `update` action helper invocation.
130
+ * Defines a simple equality-based `filter` helper that passes the filter params
131
+ to `Sequel::Dataset#where`.
132
+ * Defines a `sort` helper that applies `Sequel.asc` and `Sequel.desc` to the
133
+ sort terms and passes them to `Sequel::Dataset#order`.
134
+ * Defines a `finalize` helper that simply calls `Sequel::Dataset#all`.
135
+
136
+ If the `:pagination` Sequel extension is loaded, it also does the following:
137
+
138
+ * Configures `page_using` for page number- and size-based pagination, with an
139
+ additional record count parameter to avoid repetitive `SELECT COUNT` queries
140
+ while paging.
141
+ * Defines a `page` helper that calls `Sequel::Dataset#paginate` and computes a
142
+ hash of page params that Sinja will use to construct the root pagination
143
+ links and add to the root metadata of the response.
144
+
145
+ You may override any of the installed helpers by defining your own. Please see
146
+ the [Sinja][1] documentation for more information about Sinja hooks and
147
+ configurables, and the [Sequel][2] documentation for more information about
148
+ Sequel plugins and features.
149
+
150
+ ### Helpers
151
+
152
+ Include
153
+ [Sinja::Sequel::Helpers](/extensions/sequel/lib/sinja/sequel/helpers.rb) after
154
+ registering Sinja:
155
+
156
+ ```ruby
157
+ require 'sinja'
158
+ require 'sinja/sequel/helpers'
159
+
160
+ class MyApp < Sinatra::Base
161
+ register Sinja
162
+
163
+ helpers Sinja::Sequel::Helpers
164
+ end
165
+ ```
166
+
167
+ This is the most common use-case. **Note that including Helpers will
168
+ automatically prepend Core!**
169
+
170
+ #### `next_pk`
171
+
172
+ A convenience method to always return the primary key of the resource and the
173
+ resource from your `create` action helpers. Simply use it instead of `next`!
174
+
175
+ ```ruby
176
+ create do |attr|
177
+ next_pk Foo.create(attr)
178
+ end
179
+ ```
180
+
181
+ #### `add_missing`
182
+
183
+ Take the key of a Sequel \*_to_many association and an array of resource
184
+ identifier objects and add the "missing" records to the collection. Makes
185
+ writing your `merge` action helpers a breeze!
186
+
187
+ ```ruby
188
+ has_many :bars do
189
+ merge do |rios|
190
+ add_missing(:bars, rios)
191
+ end
192
+ end
193
+ ```
194
+
195
+ It will try to cast the ID of each resource identifier object by sending it the
196
+ `:to_i` method; pass in a third argument to specify a different method (e.g. if
197
+ the primary key of the `bars` table is a `varchar`, pass in `:to_s` instead).
198
+
199
+ This helper also takes an optional block that can be used to filter
200
+ subresources during processing. Simply return a truthy or falsey value from the
201
+ block (or raise an error to abort the entire transaction):
202
+
203
+ ```ruby
204
+ has_many :bars do
205
+ merge do |rios|
206
+ add_missing(:bars, rios) do |bar|
207
+ role?(:admin) || bar.owner == resource.owner
208
+ end
209
+ end
210
+ end
211
+ ```
212
+
213
+ #### `remove_present`
214
+
215
+ Like `add_missing`, but removes the "present" records from the collection.
216
+ Makes writing your `subtract` action helpers a breeze!
217
+
218
+ #### `add_remove`
219
+
220
+ Like `add_missing` and `remove_present`, but performs an efficient delta
221
+ operation on the collection. Makes writing your `replace` action helpers a
222
+ breeze!
223
+
224
+ ### Extension
225
+
226
+ Register [Sinja::Sequel](/extensions/sequel/lib/sinja/sequel.rb) after
227
+ registering Sinja:
228
+
229
+ ```ruby
230
+ require 'sinja'
231
+ require 'sinja/sequel'
232
+
233
+ class MyApp < Sinatra::Base
234
+ register Sinja
235
+ register Sinja::Sequel
236
+ end
237
+ ```
238
+
239
+ **Note that registering the extension will automatically include Helpers!**
240
+
241
+ After registering the extension, the `resource`, `has_many`, and `has_one` DSL
242
+ keywords will generate basic action helpers. The default `create` action helper
243
+ does not support client-generated IDs. These action helpers can be subsequently
244
+ overridden, customized by setting action helper options (i.e. `:roles`) and/or
245
+ defining `before_<action>` hooks, or removed entirely with `remove_<action>`.
246
+
247
+ ## Development
248
+
249
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
250
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
251
+ prompt that will allow you to experiment.
252
+
253
+ To install this gem onto your local machine, run `bundle exec rake install`. To
254
+ release a new version, update the version number in `version.rb`, and then run
255
+ `bundle exec rake release`, which will create a git tag for the version, push
256
+ git commits and tags, and push the `.gem` file to
257
+ [rubygems.org](https://rubygems.org).
258
+
259
+ ## Contributing
260
+
261
+ Bug reports and pull requests are welcome on GitHub at
262
+ https://github.com/mwpastore/sinja.
263
+
264
+ ## License
265
+
266
+ The gem is available as open source under the terms of the [MIT
267
+ License](http://opensource.org/licenses/MIT).
268
+
269
+ [1]: https://github.com/mwpastore/sinja
270
+ [2]: http://sequel.jeremyevans.net
271
+ [3]: https://github.com/mwpastore/sinja/tree/master/demo-app
272
+ [4]: https://github.com/sinatra/sinatra/issues/1213
273
+ [5]: https://github.com/fotinakis/jsonapi-serializers
274
+ [6]: https://github.com/fotinakis/jsonapi-serializers/pull/31#issuecomment-148193366
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
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
9
+
10
+ task default: :test