turboboost 0.0.5.1
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 +15 -0
- data/.gitignore +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +115 -0
- data/README.md +159 -0
- data/Rakefile +15 -0
- data/app/assets/javascripts/turboboost.js.coffee +64 -0
- data/lib/turboboost.rb +148 -0
- data/lib/turboboost/version.rb +3 -0
- data/test/posts_test.rb +115 -0
- data/test/test_helper.rb +35 -0
- data/turboboost.gemspec +28 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
M2Q2OTNjMmJmNmVmZWJkMGI0YmYxOTRiMzE3OTk1Y2Y2OTUzM2Q4ZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZDc0YTA2OGM4NDcyMjdlMDQ4NzY5YmMwZjMyYTgwMTJjY2IzYTc0NA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MTE3Yjc2MWIzZGI5YjVlYWRlY2FjZjg2ZDRkZDZiMTAzOTRjN2U0OTVkMzEw
|
10
|
+
NjM4MmUzMWUyYzhiY2RlNTM1YjJhOTg0OWM1ZDcwMTU4MmJhNGRmYjBhM2Fk
|
11
|
+
MzZhOWU3YjlhYThkY2E4NDliNThhMjQyMGY5MjMxMWFhODY3Njg=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjBiNWRkMGQxMWM3YTVkZGI0NGNkNjc1MThlNWI3MjdmYWQ3ZjdlMmExNDBi
|
14
|
+
OWRmMTExNzE2ZWZmNWViMGMyYTcwMDlkMmU1NTYxNTAwNjQwMzRkNjc3NTcw
|
15
|
+
YWFmODQ4ODRhYmE0YTM0YmEzYmFjMTBmMmVlNzMwYjU2OTlmM2I=
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/*.gem
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
turboboost (0.0.5)
|
5
|
+
coffee-rails
|
6
|
+
jquery-rails
|
7
|
+
railties
|
8
|
+
turbolinks
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
actionmailer (4.0.1)
|
14
|
+
actionpack (= 4.0.1)
|
15
|
+
mail (~> 2.5.4)
|
16
|
+
actionpack (4.0.1)
|
17
|
+
activesupport (= 4.0.1)
|
18
|
+
builder (~> 3.1.0)
|
19
|
+
erubis (~> 2.7.0)
|
20
|
+
rack (~> 1.5.2)
|
21
|
+
rack-test (~> 0.6.2)
|
22
|
+
activemodel (4.0.1)
|
23
|
+
activesupport (= 4.0.1)
|
24
|
+
builder (~> 3.1.0)
|
25
|
+
activerecord (4.0.1)
|
26
|
+
activemodel (= 4.0.1)
|
27
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
28
|
+
activesupport (= 4.0.1)
|
29
|
+
arel (~> 4.0.0)
|
30
|
+
activerecord-deprecated_finders (1.0.3)
|
31
|
+
activesupport (4.0.1)
|
32
|
+
i18n (~> 0.6, >= 0.6.4)
|
33
|
+
minitest (~> 4.2)
|
34
|
+
multi_json (~> 1.3)
|
35
|
+
thread_safe (~> 0.1)
|
36
|
+
tzinfo (~> 0.3.37)
|
37
|
+
ansi (1.4.3)
|
38
|
+
arel (4.0.1)
|
39
|
+
atomic (1.1.14)
|
40
|
+
awesome_print (1.2.0)
|
41
|
+
builder (3.1.4)
|
42
|
+
coffee-rails (4.0.1)
|
43
|
+
coffee-script (>= 2.2.0)
|
44
|
+
railties (>= 4.0.0, < 5.0)
|
45
|
+
coffee-script (2.2.0)
|
46
|
+
coffee-script-source
|
47
|
+
execjs
|
48
|
+
coffee-script-source (1.6.3)
|
49
|
+
erubis (2.7.0)
|
50
|
+
execjs (2.0.2)
|
51
|
+
hike (1.2.3)
|
52
|
+
i18n (0.6.5)
|
53
|
+
jquery-rails (3.0.4)
|
54
|
+
railties (>= 3.0, < 5.0)
|
55
|
+
thor (>= 0.14, < 2.0)
|
56
|
+
mail (2.5.4)
|
57
|
+
mime-types (~> 1.16)
|
58
|
+
treetop (~> 1.4.8)
|
59
|
+
metaclass (0.0.1)
|
60
|
+
mime-types (1.25)
|
61
|
+
minitest (4.7.5)
|
62
|
+
mocha (0.14.0)
|
63
|
+
metaclass (~> 0.0.1)
|
64
|
+
multi_json (1.8.2)
|
65
|
+
polyglot (0.3.3)
|
66
|
+
rack (1.5.2)
|
67
|
+
rack-test (0.6.2)
|
68
|
+
rack (>= 1.0)
|
69
|
+
rails (4.0.1)
|
70
|
+
actionmailer (= 4.0.1)
|
71
|
+
actionpack (= 4.0.1)
|
72
|
+
activerecord (= 4.0.1)
|
73
|
+
activesupport (= 4.0.1)
|
74
|
+
bundler (>= 1.3.0, < 2.0)
|
75
|
+
railties (= 4.0.1)
|
76
|
+
sprockets-rails (~> 2.0.0)
|
77
|
+
railties (4.0.1)
|
78
|
+
actionpack (= 4.0.1)
|
79
|
+
activesupport (= 4.0.1)
|
80
|
+
rake (>= 0.8.7)
|
81
|
+
thor (>= 0.18.1, < 2.0)
|
82
|
+
rake (10.1.0)
|
83
|
+
sprockets (2.10.0)
|
84
|
+
hike (~> 1.2)
|
85
|
+
multi_json (~> 1.0)
|
86
|
+
rack (~> 1.0)
|
87
|
+
tilt (~> 1.1, != 1.3.0)
|
88
|
+
sprockets-rails (2.0.1)
|
89
|
+
actionpack (>= 3.0)
|
90
|
+
activesupport (>= 3.0)
|
91
|
+
sprockets (~> 2.8)
|
92
|
+
thor (0.18.1)
|
93
|
+
thread_safe (0.1.3)
|
94
|
+
atomic
|
95
|
+
tilt (1.4.1)
|
96
|
+
treetop (1.4.15)
|
97
|
+
polyglot
|
98
|
+
polyglot (>= 0.3.1)
|
99
|
+
turbolinks (1.3.0)
|
100
|
+
coffee-rails
|
101
|
+
turn (0.9.6)
|
102
|
+
ansi
|
103
|
+
tzinfo (0.3.38)
|
104
|
+
|
105
|
+
PLATFORMS
|
106
|
+
ruby
|
107
|
+
|
108
|
+
DEPENDENCIES
|
109
|
+
awesome_print
|
110
|
+
bundler (~> 1.3)
|
111
|
+
mocha
|
112
|
+
rails (>= 3.2, < 5)
|
113
|
+
rake
|
114
|
+
turboboost!
|
115
|
+
turn
|
data/README.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
## Turboboost
|
2
|
+
|
3
|
+
Turboboost extends the power of Turbolinks into the forms of your Rails app and provides convenient success and error handlers. It aims to be a seemless and logical addition to any Turbolinks-rocking Rails 3.2+/4+ app. The main features are:
|
4
|
+
|
5
|
+
* Response redirection is handled by Turbolinks.
|
6
|
+
|
7
|
+
* Customizable success and error handling through registered JavaScript events, with optional error rendering built-in.
|
8
|
+
|
9
|
+
* Responses can also be rendered within the DOM using jQuery selectors.
|
10
|
+
|
11
|
+
<!-- The main features are: -->
|
12
|
+
|
13
|
+
<!-- * Integration into Turbolinks-managed browser history states. -->
|
14
|
+
|
15
|
+
<!-- * Faster loading upon successful form submissions with redirects, as only the body is swapped out. -->
|
16
|
+
|
17
|
+
<!-- * Customizable success and error handling through registered JavaScript events, with optional error rendering built-in. -->
|
18
|
+
|
19
|
+
<!-- As a bonus: since failed form submissions are caught and returned with JavaScript, you can cache your views harder since you don't have to re-render your form view with the model in an invalid state. -->
|
20
|
+
|
21
|
+
<!-- ### Design Pattern -->
|
22
|
+
|
23
|
+
<!-- In order to bring AJAX control over your Rails app's forms in a Turbolinks compatible way, you have to define some assumptions. The way Turboboost currently works is: -->
|
24
|
+
|
25
|
+
<!-- * For GET requests, visit the form's action with the serialized data appended to as a query string with Turbolinks. This will preserve navigable history states for things like search filter forms. -->
|
26
|
+
<!-- * For other request types, hit your Rails controllers then: -->
|
27
|
+
<!-- - If the response has a `redirect_to` declaration, do not reload. Instead, visit that route with Turbolinks. -->
|
28
|
+
<!-- - If there is an error, don't visit anything with Turbolinks. Instead, the errors will be sent through the global document event `turboboost:error`. Optionally, the errors can be prepended to the form as HTML. -->
|
29
|
+
<!-- * Turboboost only works on forms that you define with `turboboost: true` in your Rails form helper options or manually with a `data-turboboost` attribute. -->
|
30
|
+
<!-- * When a Turboboost has an AJAX request in process, do sensible things like disable that form's submit button. -->
|
31
|
+
|
32
|
+
<!-- These are definitely open to discussion. The goal here is to be Rails 3.2+ and Rails 4+ compatible. -->
|
33
|
+
|
34
|
+
### Installation
|
35
|
+
|
36
|
+
``` ruby
|
37
|
+
gem "turboboost", github: "waymondo/turboboost"
|
38
|
+
```
|
39
|
+
|
40
|
+
Put that in your `Gemfile` and `bundle install`. In your `application.js` require it after `jquery_ujs` and `turbolinks`:
|
41
|
+
|
42
|
+
``` javascript
|
43
|
+
//= require jquery_ujs
|
44
|
+
//= require turbolinks
|
45
|
+
//= require turboboost
|
46
|
+
```
|
47
|
+
|
48
|
+
Then in your view files, add `turboboost: true` to your form helpers:
|
49
|
+
|
50
|
+
```
|
51
|
+
= form_for :resource, turboboost: true do |f| ...
|
52
|
+
```
|
53
|
+
|
54
|
+
or add the data attribute manually:
|
55
|
+
|
56
|
+
```
|
57
|
+
<form data-turboboost> ...
|
58
|
+
```
|
59
|
+
|
60
|
+
### Usage
|
61
|
+
|
62
|
+
#### Redirection with Turbolinks
|
63
|
+
|
64
|
+
In its simplest server-side implementation, a basic Turboboost controller action with redirection might look like this:
|
65
|
+
|
66
|
+
``` ruby
|
67
|
+
def create
|
68
|
+
post = Post.create!(params[:post]) <- trigger exception if model is invalid
|
69
|
+
redirect_to post, notice: 'Post was successfully created.'
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
If the post is successfully created, the app will visit the `post_url` with Turbolinks if it was sent from a Turboboost. Otherwise, the redirect will happen like normal.
|
74
|
+
|
75
|
+
#### Error Handling and Flash Messages
|
76
|
+
|
77
|
+
If the post in our example above is invalid, no redirect will happen and a `rescue_from` handler will pass off the errors to JavaScript through the `turboboost:error` event:
|
78
|
+
|
79
|
+
``` coffeescript
|
80
|
+
$(document).on "turboboost:error", (e, errors) ->
|
81
|
+
console.log(errors) # <- JSON array of errors messages
|
82
|
+
```
|
83
|
+
|
84
|
+
You can also trigger the JSON error messages explicitly with the method `render_turboboost_errors_for(record)` if you don't want to use the `rescue_from` handler:
|
85
|
+
|
86
|
+
``` ruby
|
87
|
+
def create
|
88
|
+
@post = Post.new(post_params)
|
89
|
+
if @post.save
|
90
|
+
redirect_to @post, notice: 'Post was successfully created.'
|
91
|
+
else
|
92
|
+
respond_to do |format|
|
93
|
+
format.html { render 'new' }
|
94
|
+
format.js { render_turboboost_errors_for(@post) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
Optionally, Turboboost can render returned errors with the same HTML structure used in the default Rails generators and prepend it to the form. The HTML structure looks like this:
|
101
|
+
|
102
|
+
``` html
|
103
|
+
<div id="error_explanation">
|
104
|
+
<ul>
|
105
|
+
{{#errors}}
|
106
|
+
<li>{{this}}</li>
|
107
|
+
{{/errors}}
|
108
|
+
</ul>
|
109
|
+
</div>
|
110
|
+
```
|
111
|
+
|
112
|
+
To turn it on:
|
113
|
+
|
114
|
+
``` coffeescript
|
115
|
+
Turboboost.insertErrors = true
|
116
|
+
```
|
117
|
+
|
118
|
+
Currently Turboboost will handle invalid `ActiveRecord` and `ActiveModel` error messages as well as basic HTTP error messages.
|
119
|
+
|
120
|
+
There is also a `turboboost:success` event that is triggered and passed a hash of all current flash messages if they are present:
|
121
|
+
|
122
|
+
``` coffeescript
|
123
|
+
$(document).on "turboboost:success", (e, flash) ->
|
124
|
+
console.log(flash) # -> {'notice': 'Post was successfully created.'}
|
125
|
+
```
|
126
|
+
|
127
|
+
You may also prevent redirecting on any Turboboost by adding the attribute `data-no-turboboost-redirect` to your form element if you just want to handle the response and returned flash messages manually:
|
128
|
+
|
129
|
+
#### Scoped response rendering
|
130
|
+
|
131
|
+
Turboboost also provides some extra rendering options for letting you render your form responses at specific locations in the DOM:
|
132
|
+
|
133
|
+
|Rails controller render option | jQuery function|
|
134
|
+
|-------------------------------|:---------------|
|
135
|
+
|`:within` |`html()` |
|
136
|
+
|`:replace` |`replaceWith()` |
|
137
|
+
|`:prepend` |`prepend()` |
|
138
|
+
|`:append` |`append()` |
|
139
|
+
|
140
|
+
The value can be any jQuery selector. Example usage:
|
141
|
+
|
142
|
+
``` ruby
|
143
|
+
respond_to do |format|
|
144
|
+
format.js { render partial: 'task', object: @task, prepend: "#todo-list" }
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
or:
|
149
|
+
|
150
|
+
``` ruby
|
151
|
+
respond_to do |format|
|
152
|
+
format.js { render partial: 'task', object: @task, replace: "#todo-item#{@task.id}" }
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
### Todo
|
157
|
+
|
158
|
+
* More tests, obviously.
|
159
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
desc 'Run tests for Turboboost.'
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.libs << 'lib'
|
9
|
+
t.libs << 'test'
|
10
|
+
t.pattern = 'test/*_test.rb'
|
11
|
+
t.verbose = true
|
12
|
+
end
|
13
|
+
|
14
|
+
# Make test the default task.
|
15
|
+
task :default => :test
|
@@ -0,0 +1,64 @@
|
|
1
|
+
@Turboboost =
|
2
|
+
insertErrors: false
|
3
|
+
defaultError: "Sorry, there was an error."
|
4
|
+
|
5
|
+
turboboost = "form[data-turboboost]"
|
6
|
+
errID = "#error_explanation"
|
7
|
+
errTemplate = (errors) ->
|
8
|
+
"<ul><li>#{$.makeArray(errors).join('</li><li>')}</li></ul>"
|
9
|
+
|
10
|
+
enableForm = ($form) ->
|
11
|
+
$form.find("[type='submit']").removeAttr('disabled')
|
12
|
+
|
13
|
+
disableForm = ($form) ->
|
14
|
+
$form.find("[type='submit']").attr('disabled', 'disabled')
|
15
|
+
|
16
|
+
insertErrors = (e, errors) ->
|
17
|
+
return if !Turboboost.insertErrors
|
18
|
+
errors = [Turboboost.defaultError] if !errors.length
|
19
|
+
$form = $(e.target)
|
20
|
+
$el = $form.find(errID)
|
21
|
+
if !$el.length
|
22
|
+
$form.prepend $el = $("<div id='#{errID.substr(1)}'></div>")
|
23
|
+
$el.html errTemplate(errors)
|
24
|
+
|
25
|
+
tryJSONParse = (str) ->
|
26
|
+
try
|
27
|
+
JSON.parse str
|
28
|
+
catch e
|
29
|
+
null
|
30
|
+
|
31
|
+
maybeInsertSuccessResponseBody = (resp) ->
|
32
|
+
if (scope = resp.getResponseHeader('X-Within'))
|
33
|
+
$(scope).html(resp.responseText)
|
34
|
+
else if (scope = resp.getResponseHeader('X-Replace'))
|
35
|
+
$(scope).replaceWith(resp.responseText)
|
36
|
+
else if (scope = resp.getResponseHeader('X-Append'))
|
37
|
+
$(scope).append(resp.responseText)
|
38
|
+
else if (scope = resp.getResponseHeader('X-Prepend'))
|
39
|
+
$(scope).prepend(resp.responseText)
|
40
|
+
|
41
|
+
$(document)
|
42
|
+
.on "ajax:beforeSend", turboboost, (e, xhr, settings) ->
|
43
|
+
xhr.setRequestHeader('X-Turboboost', '1')
|
44
|
+
disableForm $(e.target)
|
45
|
+
if settings.type == "GET"
|
46
|
+
Turbolinks.visit [@action, $(@).serialize()].join("?")
|
47
|
+
return false
|
48
|
+
|
49
|
+
.on "ajax:complete", turboboost, (e, resp) ->
|
50
|
+
$form = $(e.target)
|
51
|
+
|
52
|
+
if resp.status in [200..299]
|
53
|
+
$form.trigger "turboboost:success", tryJSONParse resp.getResponseHeader('X-Flash')
|
54
|
+
if (location = resp.getResponseHeader('Location')) and !$form.attr('data-no-turboboost-redirect')
|
55
|
+
Turbolinks.visit(location)
|
56
|
+
else
|
57
|
+
enableForm $form
|
58
|
+
maybeInsertSuccessResponseBody(resp)
|
59
|
+
|
60
|
+
if resp.status in [400..599]
|
61
|
+
enableForm $form
|
62
|
+
$form.trigger "turboboost:error", tryJSONParse resp.responseText
|
63
|
+
|
64
|
+
.on "turboboost:error", insertErrors
|
data/lib/turboboost.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'turboboost/version'
|
2
|
+
|
3
|
+
CATCHABLE_ERRORS = {
|
4
|
+
"EOFError" => 500,
|
5
|
+
"Errno::ECONNRESET" => 500,
|
6
|
+
"Errno::EINVAL" => 500,
|
7
|
+
"Timeout::Error" => :request_timeout,
|
8
|
+
"Net::HTTPBadResponse" => 500,
|
9
|
+
"Net::HTTPHeaderSyntaxError" => 500,
|
10
|
+
"Net::ProtocolError" => 500,
|
11
|
+
"ActiveRecord::RecordNotFound" => :not_found,
|
12
|
+
"ActiveRecord::StaleObjectError" => :conflict,
|
13
|
+
"ActiveRecord::RecordInvalid" => :unprocessable_entity,
|
14
|
+
"ActiveRecord::RecordNotSaved" => :unprocessable_entity,
|
15
|
+
"ActiveModel::StrictValidationFailed" => :unprocessable_entity,
|
16
|
+
"ActiveModel::MissingAttributeError" => :unprocessable_entity
|
17
|
+
}
|
18
|
+
|
19
|
+
module Turboboost
|
20
|
+
|
21
|
+
module Controller
|
22
|
+
extend ActiveSupport::Concern
|
23
|
+
|
24
|
+
included do
|
25
|
+
send :rescue_from, *(CATCHABLE_ERRORS.keys), with: :turboboost_error_handler
|
26
|
+
end
|
27
|
+
|
28
|
+
def turboboost_error_handler(error)
|
29
|
+
if request.xhr? and request.headers['HTTP_X_TURBOBOOST']
|
30
|
+
error_status = CATCHABLE_ERRORS[error.class.name]
|
31
|
+
if defined?(error.record)
|
32
|
+
render_turboboost_errors_for(error.record)
|
33
|
+
else
|
34
|
+
render json: [error.message], status: error_status || 500
|
35
|
+
end
|
36
|
+
else
|
37
|
+
raise error
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def render_turboboost_errors_for(record)
|
42
|
+
render json: record.errors.full_messages.to_a, status: :unprocessable_entity, root: false
|
43
|
+
end
|
44
|
+
|
45
|
+
def head_turboboost_success(turboboost_flash={})
|
46
|
+
turboboost_flash = _turboboost_get_flash_messages(turboboost_flash)
|
47
|
+
head :ok, "X-Flash" => turboboost_flash.to_json
|
48
|
+
end
|
49
|
+
|
50
|
+
def render(*args, &block)
|
51
|
+
if request.xhr? and request.headers['HTTP_X_TURBOBOOST']
|
52
|
+
turboboost_render(*args, &block)
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def turboboost_render(*args, &block)
|
59
|
+
options = _normalize_render(*args, &block)
|
60
|
+
[:replace, :within, :append, :prepend].each do |h|
|
61
|
+
response.headers["X-#{h.capitalize}"] = options[h] if options[h]
|
62
|
+
end
|
63
|
+
self.response_body = render_to_body(options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def redirect_to(options={}, response_status_and_flash={})
|
67
|
+
if request.xhr? and request.headers['HTTP_X_TURBOBOOST']
|
68
|
+
turboboost_redirect_to(options, response_status_and_flash)
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def turboboost_redirect_to(options={}, response_status_and_flash={})
|
75
|
+
raise ActionControllerError.new("Cannot redirect to nil!") unless options
|
76
|
+
raise AbstractController::DoubleRenderError if response_body
|
77
|
+
|
78
|
+
# set flash for turbo redirect headers
|
79
|
+
turboboost_flash = _turboboost_get_flash_messages(response_status_and_flash)
|
80
|
+
|
81
|
+
self.location = _compute_redirect_to_location(options)
|
82
|
+
head :ok, "X-Flash" => turboboost_flash.to_json
|
83
|
+
|
84
|
+
flash.update(turboboost_flash) # set flash for rendered view
|
85
|
+
end
|
86
|
+
|
87
|
+
def _turboboost_get_flash_messages(response_status_and_flash={})
|
88
|
+
turboboost_flash = {}
|
89
|
+
flash_types = defined?(self.class._flash_types) ? self.class._flash_types : [:alert, :notice]
|
90
|
+
flash_types.each do |flash_type|
|
91
|
+
if type = response_status_and_flash.delete(flash_type)
|
92
|
+
turboboost_flash[flash_type] = type
|
93
|
+
end
|
94
|
+
end
|
95
|
+
if other_flashes = response_status_and_flash.delete(:flash)
|
96
|
+
turboboost_flash.update(other_flashes)
|
97
|
+
end
|
98
|
+
turboboost_flash
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
# borrowed from - https://github.com/fs/turboforms/blob/master/lib/turboforms.rb
|
104
|
+
module FormHelper
|
105
|
+
extend ActiveSupport::Concern
|
106
|
+
|
107
|
+
included do
|
108
|
+
alias_method_chain :form_for, :data_turboboost
|
109
|
+
alias_method_chain :form_tag, :data_turboboost
|
110
|
+
end
|
111
|
+
|
112
|
+
def form_for_with_data_turboboost(record_or_name_or_array, *args, &proc)
|
113
|
+
options = args.extract_options!
|
114
|
+
|
115
|
+
if options.has_key?(:turboboost) && options.delete(:turboboost)
|
116
|
+
options[:html] ||= {}
|
117
|
+
options[:html]["data-turboboost"] = true
|
118
|
+
options[:remote] = true
|
119
|
+
end
|
120
|
+
|
121
|
+
form_for_without_data_turboboost(record_or_name_or_array, *(args << options), &proc)
|
122
|
+
end
|
123
|
+
|
124
|
+
def form_tag_with_data_turboboost(record_or_name_or_array, *args, &proc)
|
125
|
+
options = args.extract_options!
|
126
|
+
|
127
|
+
if options.has_key?(:turboboost) && options.delete(:turboboost)
|
128
|
+
options[:data] ||= {}
|
129
|
+
options[:data]["turboboost"] = true
|
130
|
+
options[:remote] = true
|
131
|
+
end
|
132
|
+
|
133
|
+
form_tag_without_data_turboboost(record_or_name_or_array, *(args << options), &proc)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Engine < Rails::Engine
|
138
|
+
initializer :turboboost do
|
139
|
+
ActionView::Base.send :include, Turboboost::FormHelper
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
# ActionView::Base.send :include, Turboboost::FormHelper
|
146
|
+
ActiveSupport.on_load(:action_controller) do
|
147
|
+
include Turboboost::Controller
|
148
|
+
end
|
data/test/posts_test.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class Post
|
4
|
+
extend ActiveModel::Naming
|
5
|
+
|
6
|
+
include ActiveModel::Conversion
|
7
|
+
include ActiveModel::Model
|
8
|
+
include ActiveModel::Validations
|
9
|
+
|
10
|
+
attr_accessor :title
|
11
|
+
|
12
|
+
validates! :title, length: { minimum: 1, message: "is too short." }
|
13
|
+
|
14
|
+
def self.create!(attributes)
|
15
|
+
post = self.new(attributes)
|
16
|
+
post.save
|
17
|
+
end
|
18
|
+
|
19
|
+
def save
|
20
|
+
valid?
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_attributes!(attributes)
|
24
|
+
attributes = attributes
|
25
|
+
valid?
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class PostsController < ApplicationController
|
31
|
+
|
32
|
+
respond_to :html
|
33
|
+
respond_to :js, only: [:create, :update, :destroy]
|
34
|
+
|
35
|
+
def index; end
|
36
|
+
def show; end
|
37
|
+
def new; end
|
38
|
+
def edit; end
|
39
|
+
|
40
|
+
def create
|
41
|
+
post = Post.create!(params[:post])
|
42
|
+
redirect_to posts_url, notice: "Post was successfully created."
|
43
|
+
end
|
44
|
+
|
45
|
+
def update
|
46
|
+
post = Post.find(params[:id])
|
47
|
+
post.update_attributes!
|
48
|
+
redirect_to post_url(post), notice: "Post was successfully updated."
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy
|
52
|
+
post = Post.find(params[:id])
|
53
|
+
post.destroy!
|
54
|
+
redirect_to posts_url
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
class PostsVerboseController < PostsController
|
60
|
+
|
61
|
+
def create
|
62
|
+
post = post.new(params[:post])
|
63
|
+
if post.save
|
64
|
+
redirect_to post_url(post)
|
65
|
+
else
|
66
|
+
respond_to do |format|
|
67
|
+
format.html { redirect_to posts_url }
|
68
|
+
format.js { render_turboboost_errors_for(post) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def update
|
74
|
+
post = post.find(params[:id])
|
75
|
+
if post.update_attributes(params[:post])
|
76
|
+
redirect_to post_url(post)
|
77
|
+
else
|
78
|
+
respond_to do |format|
|
79
|
+
format.html { redirect_to posts_url }
|
80
|
+
format.js { render_turboboost_errors_for(post) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
class PostsControllerTest < ActionController::TestCase
|
88
|
+
|
89
|
+
tests PostsController
|
90
|
+
|
91
|
+
setup do
|
92
|
+
@request.headers["X-Turboboost"] = "1"
|
93
|
+
end
|
94
|
+
|
95
|
+
test "On a successful turboboost request, return an empty response with headers containing the redirect location and flash message" do
|
96
|
+
xhr :post, :create, post: { title: "Foo" }
|
97
|
+
|
98
|
+
assert @response.body.strip.blank?
|
99
|
+
assert_equal flash[:notice], 'Post was successfully created.'
|
100
|
+
assert_equal @response.headers["Location"], posts_url
|
101
|
+
assert_equal JSON.parse(@response.headers["X-Flash"])["notice"], 'Post was successfully created.'
|
102
|
+
end
|
103
|
+
|
104
|
+
test "On an unsuccessful turboboost request, catch and return the error message(s) as an array" do
|
105
|
+
xhr :post, :create, post: { title: "" }
|
106
|
+
|
107
|
+
assert_equal @response.status, 422
|
108
|
+
assert_equal @response.body.strip, ["Title is too short."].to_json
|
109
|
+
end
|
110
|
+
|
111
|
+
test "On an unsuccessful turboboost request, explicitly render the error message(s)" do
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
ENV["RAILS_ENV"] = "test"
|
6
|
+
|
7
|
+
require 'rails'
|
8
|
+
require "active_record"
|
9
|
+
require "action_controller"
|
10
|
+
require "action_controller"
|
11
|
+
require "action_controller/railtie"
|
12
|
+
|
13
|
+
require 'rails/test_help'
|
14
|
+
require 'awesome_print'
|
15
|
+
|
16
|
+
require 'turboboost'
|
17
|
+
|
18
|
+
class TestApp < Rails::Application; end
|
19
|
+
Rails.application = TestApp
|
20
|
+
Rails.configuration.secret_key_base = "abc123"
|
21
|
+
|
22
|
+
Turboboost::Routes = ActionDispatch::Routing::RouteSet.new
|
23
|
+
Turboboost::Routes.draw do
|
24
|
+
resources 'posts'
|
25
|
+
end
|
26
|
+
|
27
|
+
class ApplicationController < ActionController::Base
|
28
|
+
include Turboboost::Routes.url_helpers
|
29
|
+
end
|
30
|
+
|
31
|
+
class ActiveSupport::TestCase
|
32
|
+
setup do
|
33
|
+
@routes = Turboboost::Routes
|
34
|
+
end
|
35
|
+
end
|
data/turboboost.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'turboboost/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "turboboost"
|
8
|
+
spec.version = Turboboost::VERSION
|
9
|
+
spec.authors = ["Justin Talbott"]
|
10
|
+
spec.email = ["justin@waymondo.com"]
|
11
|
+
spec.description = %q{Turbolinks and AJAX enhancements for your Rails forms and links.}
|
12
|
+
spec.summary = %q{Turbolinks and AJAX enhancements for your Rails forms and links.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'railties'
|
22
|
+
spec.add_dependency 'turbolinks'
|
23
|
+
spec.add_dependency 'coffee-rails'
|
24
|
+
spec.add_dependency 'jquery-rails'
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: turboboost
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Talbott
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: railties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: turbolinks
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: coffee-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: jquery-rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Turbolinks and AJAX enhancements for your Rails forms and links.
|
98
|
+
email:
|
99
|
+
- justin@waymondo.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- Gemfile
|
106
|
+
- Gemfile.lock
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- app/assets/javascripts/turboboost.js.coffee
|
110
|
+
- lib/turboboost.rb
|
111
|
+
- lib/turboboost/version.rb
|
112
|
+
- test/posts_test.rb
|
113
|
+
- test/test_helper.rb
|
114
|
+
- turboboost.gemspec
|
115
|
+
homepage: ''
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 2.1.11
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Turbolinks and AJAX enhancements for your Rails forms and links.
|
139
|
+
test_files:
|
140
|
+
- test/posts_test.rb
|
141
|
+
- test/test_helper.rb
|
142
|
+
has_rdoc:
|