sinja 1.1.0.pre4 → 1.2.0.pre2

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.
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