turboboost 0.0.11 → 0.0.12
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 +1 -0
- data/Appraisals +17 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +6 -44
- data/README.md +36 -22
- data/Rakefile +2 -0
- data/app/assets/javascripts/turboboost.js.coffee +19 -7
- data/gemfiles/rails_3_2.gemfile +13 -0
- data/gemfiles/rails_3_2.gemfile.lock +135 -0
- data/gemfiles/rails_4_0.gemfile +12 -0
- data/gemfiles/rails_4_0.gemfile.lock +116 -0
- data/gemfiles/rails_4_1.gemfile +12 -0
- data/gemfiles/rails_4_1.gemfile.lock +121 -0
- data/gemfiles/rails_4_2.gemfile +13 -0
- data/gemfiles/rails_4_2.gemfile.lock +150 -0
- data/lib/turboboost.rb +2 -159
- data/lib/turboboost/controller.rb +111 -0
- data/lib/turboboost/form_helper.rb +46 -0
- data/lib/turboboost/version.rb +1 -1
- data/test/controller_test.rb +51 -88
- data/test/controllers/items_controller.rb +23 -0
- data/test/controllers/posts_controller.rb +27 -0
- data/test/controllers/users_controller.rb +32 -0
- data/test/locales/en.yml +4 -0
- data/test/test_helper.rb +27 -15
- data/test/view_test.rb +9 -11
- data/test/views/items/show.html.erb +1 -0
- metadata +24 -3
@@ -0,0 +1,111 @@
|
|
1
|
+
module Turboboost
|
2
|
+
module Controller
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
CATCHABLE_ERRORS = {
|
6
|
+
'EOFError' => 500,
|
7
|
+
'Errno::ECONNRESET' => 500,
|
8
|
+
'Errno::EINVAL' => 500,
|
9
|
+
'Timeout::Error' => :request_timeout,
|
10
|
+
'Net::HTTPBadResponse' => 500,
|
11
|
+
'Net::HTTPHeaderSyntaxError' => 500,
|
12
|
+
'Net::ProtocolError' => 500,
|
13
|
+
'ActiveRecord::RecordNotFound' => :not_found,
|
14
|
+
'ActiveRecord::StaleObjectError' => :conflict,
|
15
|
+
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
|
16
|
+
'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
|
17
|
+
'ActiveModel::StrictValidationFailed' => :unprocessable_entity,
|
18
|
+
'ActiveModel::MissingAttributeError' => :unprocessable_entity
|
19
|
+
}
|
20
|
+
|
21
|
+
included do
|
22
|
+
send :rescue_from, *(CATCHABLE_ERRORS.keys), with: :turboboost_error_handler
|
23
|
+
end
|
24
|
+
|
25
|
+
def turboboost_error_handler(error)
|
26
|
+
if request.xhr? && request.headers['HTTP_X_TURBOBOOST']
|
27
|
+
error_status = CATCHABLE_ERRORS[error.class.name]
|
28
|
+
response.headers['X-Turboboosted'] = '1'
|
29
|
+
if defined?(error.record)
|
30
|
+
render_turboboost_errors_for(error.record)
|
31
|
+
else
|
32
|
+
translation = I18n.t("turboboost.errors.#{error.class.name}")
|
33
|
+
message = translation.match('translation missing: (.+)') ? error.class.name : translation
|
34
|
+
render json: [message], status: error_status || 500, root: false
|
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, 'X-Turboboosted' => '1'
|
48
|
+
end
|
49
|
+
|
50
|
+
def render(*args, &block)
|
51
|
+
if request.xhr? && 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, :before, :after].each do |h|
|
61
|
+
response.headers["X-#{h.capitalize}"] = options[h] if options[h]
|
62
|
+
end
|
63
|
+
response.headers['X-Flash'] = _turboboost_get_flash_messages(options).to_json
|
64
|
+
response.headers['X-Turboboosted'] = '1'
|
65
|
+
self.response_body = render_to_body(options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def redirect_to(options = {}, response_status_and_flash = {})
|
69
|
+
if request.xhr? && request.headers['HTTP_X_TURBOBOOST']
|
70
|
+
turboboost_redirect_to(options, response_status_and_flash)
|
71
|
+
else
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def turboboost_redirect_to(options = {}, response_status_and_flash = {})
|
77
|
+
raise ActionControllerError.new('Cannot redirect to nil!') unless options
|
78
|
+
raise AbstractController::DoubleRenderError if response_body
|
79
|
+
|
80
|
+
# set flash for turbo redirect headers
|
81
|
+
turboboost_flash = _turboboost_get_flash_messages(response_status_and_flash)
|
82
|
+
|
83
|
+
if Rails.version < '4.2'
|
84
|
+
self.location = _compute_redirect_to_location(options)
|
85
|
+
else
|
86
|
+
self.location = _compute_redirect_to_location(request, options)
|
87
|
+
end
|
88
|
+
|
89
|
+
head :ok, 'X-Flash' => turboboost_flash.to_json
|
90
|
+
|
91
|
+
flash.update(turboboost_flash) # set flash for rendered view
|
92
|
+
end
|
93
|
+
|
94
|
+
def _turboboost_get_flash_messages(response_status_and_flash = {})
|
95
|
+
turboboost_flash = {}
|
96
|
+
flash_types = defined?(self.class._flash_types) ? self.class._flash_types : [:alert, :notice]
|
97
|
+
flash_types.each do |flash_type|
|
98
|
+
if type = flash.delete(type)
|
99
|
+
turboboost_flash.update(type)
|
100
|
+
end
|
101
|
+
if type = response_status_and_flash.delete(flash_type)
|
102
|
+
turboboost_flash[flash_type] = type
|
103
|
+
end
|
104
|
+
end
|
105
|
+
if other_flashes = response_status_and_flash.delete(:flash)
|
106
|
+
turboboost_flash.update(other_flashes)
|
107
|
+
end
|
108
|
+
turboboost_flash
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Turboboost
|
2
|
+
# borrowed from - https://github.com/fs/turboforms/blob/master/lib/turboforms.rb
|
3
|
+
module FormHelper
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
alias_method_chain :form_for, :data_turboboost
|
8
|
+
alias_method_chain :form_tag, :data_turboboost
|
9
|
+
end
|
10
|
+
|
11
|
+
def form_for_with_data_turboboost(record_or_name_or_array, *args, &proc)
|
12
|
+
options = args.extract_options!
|
13
|
+
|
14
|
+
if options.key?(:turboboost) && options.delete(:turboboost)
|
15
|
+
options[:html] ||= {}
|
16
|
+
options[:html]['data-turboboost'] = true
|
17
|
+
options[:remote] = true
|
18
|
+
end
|
19
|
+
|
20
|
+
form_for_without_data_turboboost(record_or_name_or_array, *(args << options), &proc)
|
21
|
+
end
|
22
|
+
|
23
|
+
def form_tag_with_data_turboboost(record_or_name_or_array, *args, &proc)
|
24
|
+
options = args.extract_options!
|
25
|
+
|
26
|
+
if options.key?(:turboboost) && options.delete(:turboboost)
|
27
|
+
options[:data] ||= {}
|
28
|
+
options[:data]['turboboost'] = true
|
29
|
+
options[:remote] = true
|
30
|
+
end
|
31
|
+
|
32
|
+
form_tag_without_data_turboboost(record_or_name_or_array, *(args << options), &proc)
|
33
|
+
end
|
34
|
+
|
35
|
+
def convert_options_to_data_attributes(options, html_options)
|
36
|
+
if html_options
|
37
|
+
html_options = html_options.stringify_keys
|
38
|
+
if html_options.delete('turboboost')
|
39
|
+
html_options['data-remote'] = 'true'
|
40
|
+
html_options['data-turboboost'] = 'true'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
super options, html_options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/turboboost/version.rb
CHANGED
data/test/controller_test.rb
CHANGED
@@ -1,124 +1,87 @@
|
|
1
1
|
require 'test_helper'
|
2
|
-
|
3
|
-
class PostsController < ApplicationController
|
4
|
-
|
5
|
-
respond_to :html
|
6
|
-
respond_to :js, only: [:create, :update, :destroy]
|
7
|
-
|
8
|
-
def create
|
9
|
-
post = Post.create!(post_params)
|
10
|
-
redirect_to posts_url, notice: "Post was successfully created."
|
11
|
-
end
|
12
|
-
|
13
|
-
def update
|
14
|
-
post = Post.find(params[:id])
|
15
|
-
post.update_attributes!(post_params)
|
16
|
-
redirect_to post_url(post), notice: "Post was successfully updated."
|
17
|
-
end
|
18
|
-
|
19
|
-
def destroy
|
20
|
-
post = Post.find(params[:id])
|
21
|
-
post.destroy!
|
22
|
-
redirect_to posts_url
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def post_params
|
28
|
-
params.require(:post).permit(:title, :user_id)
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
class UsersController < ApplicationController
|
34
|
-
|
35
|
-
def create
|
36
|
-
user = User.new(user_params)
|
37
|
-
if user.save
|
38
|
-
flash[:notice] = "User was successfully created."
|
39
|
-
redirect_to user_url(user)
|
40
|
-
else
|
41
|
-
respond_to do |format|
|
42
|
-
format.html { render :new }
|
43
|
-
format.js { render_turboboost_errors_for(user) }
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def update
|
49
|
-
user = User.find(params[:id])
|
50
|
-
if user.update_attributes(user_params)
|
51
|
-
redirect_to user_url(user)
|
52
|
-
else
|
53
|
-
respond_to do |format|
|
54
|
-
format.html { redirect_to users_url }
|
55
|
-
format.js { render_turboboost_errors_for(user) }
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def user_params
|
63
|
-
params.require(:user).permit(:name, :email)
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
2
|
+
Dir[File.dirname(__FILE__) + '/controllers/*.rb'].each { |file| require file }
|
67
3
|
|
68
4
|
class PostsControllerTest < ActionController::TestCase
|
69
|
-
|
70
5
|
tests PostsController
|
71
6
|
|
72
7
|
setup do
|
73
|
-
@request.headers[
|
8
|
+
@request.headers['X-Turboboost'] = '1'
|
74
9
|
end
|
75
10
|
|
76
|
-
test
|
77
|
-
xhr :post, :create, post: { title:
|
11
|
+
test 'On a successful turboboost request, return an empty response with headers containing the redirect location and flash message' do
|
12
|
+
xhr :post, :create, post: { title: 'Foobar', user_id: '1' }
|
78
13
|
|
79
14
|
assert @response.body.strip.blank?
|
80
15
|
assert_equal flash[:notice], 'Post was successfully created.'
|
81
|
-
assert_equal @response.headers[
|
82
|
-
assert_equal JSON.parse(@response.headers[
|
16
|
+
assert_equal @response.headers['Location'], posts_url
|
17
|
+
assert_equal JSON.parse(@response.headers['X-Flash'])['notice'], 'Post was successfully created.'
|
83
18
|
end
|
84
19
|
|
85
|
-
test
|
86
|
-
xhr :post, :create, post: { title:
|
20
|
+
test 'On an unsuccessful turboboost request, catch and return the error message(s) as an array' do
|
21
|
+
xhr :post, :create, post: { title: 'Title', user_id: nil }
|
87
22
|
|
88
23
|
assert_equal @response.status, 422
|
89
|
-
assert_equal @response.body.strip, [
|
24
|
+
assert_equal @response.body.strip, ['User can\'t be blank'].to_json
|
90
25
|
|
91
|
-
xhr :post, :create, post: { title:
|
26
|
+
xhr :post, :create, post: { title: 'Tit', user_id: nil }
|
92
27
|
|
93
28
|
assert_equal @response.status, 422
|
94
|
-
assert_equal @response.body.strip, [
|
29
|
+
assert_equal @response.body.strip, ['Title is too short.', "User can't be blank"].to_json
|
95
30
|
end
|
96
|
-
|
97
31
|
end
|
98
32
|
|
99
|
-
|
100
33
|
class UsersControllerTest < ActionController::TestCase
|
101
|
-
|
102
34
|
tests UsersController
|
103
35
|
|
104
36
|
setup do
|
105
|
-
@request.headers[
|
37
|
+
@request.headers['X-Turboboost'] = '1'
|
106
38
|
end
|
107
39
|
|
108
|
-
test
|
109
|
-
xhr :post, :create, user: { name:
|
40
|
+
test 'On a successful turboboost request, return an empty response with headers containing the redirect location and flash message' do
|
41
|
+
xhr :post, :create, user: { name: 'Mike', email: 'mike@mike.com' }
|
110
42
|
|
111
|
-
|
43
|
+
assert_equal @response.body.strip.blank?, true
|
112
44
|
assert_equal flash[:notice], 'User was successfully created.'
|
113
|
-
assert_equal @response.headers[
|
114
|
-
assert_equal JSON.parse(@response.headers[
|
45
|
+
assert_equal @response.headers['Location'], user_url(1)
|
46
|
+
assert_equal JSON.parse(@response.headers['X-Flash'])['notice'], 'User was successfully created.'
|
115
47
|
end
|
116
48
|
|
117
|
-
test
|
118
|
-
xhr :post, :create, user: { name:
|
49
|
+
test 'On an unsuccessful turboboost request, explicitly render the error message(s)' do
|
50
|
+
xhr :post, :create, user: { name: 'Mike', email: 'mike at mike.com' }
|
119
51
|
|
120
52
|
assert_equal @response.status, 422
|
121
|
-
assert_equal @response.body.strip, [
|
53
|
+
assert_equal @response.body.strip, ['Email is invalid'].to_json
|
122
54
|
end
|
55
|
+
end
|
123
56
|
|
57
|
+
class ItemsControllerTest < ActionController::TestCase
|
58
|
+
tests ItemsController
|
59
|
+
|
60
|
+
setup do
|
61
|
+
@request.headers['X-Turboboost'] = '1'
|
62
|
+
end
|
63
|
+
|
64
|
+
test 'On a failed turboboost get request, return custom internationalization messaging' do
|
65
|
+
xhr :get, :show, id: 123
|
66
|
+
i18n_message = I18n.t("turboboost.errors.ActiveRecord::RecordNotFound")
|
67
|
+
assert_equal @response.body.strip, [i18n_message].to_json
|
68
|
+
end
|
69
|
+
|
70
|
+
test 'On a successful turboboost post request, return rendering options in the headers' do
|
71
|
+
xhr :post, :create, item: { name: 'Bottle' }
|
72
|
+
|
73
|
+
assert_equal @response.headers['Location'], nil
|
74
|
+
assert_equal JSON.parse(@response.headers['X-Flash'])['notice'], 'Item was successfully created.'
|
75
|
+
assert_equal @response.headers['X-Within'], '#sidebar'
|
76
|
+
assert_equal @response.body.strip, "<div id=\"item\">Bottle</div>"
|
77
|
+
end
|
78
|
+
|
79
|
+
test 'On a successful turboboost update using render nothing: true, still return flash headers' do
|
80
|
+
@item = Item.create(name: 'Opener')
|
81
|
+
xhr :put, :update, id: @item.id, item: { name: 'Bottle Opener' }
|
82
|
+
|
83
|
+
assert_equal @response.headers['Location'], nil
|
84
|
+
assert_equal @response.body.strip, ''
|
85
|
+
assert_equal JSON.parse(@response.headers['X-Flash'])['notice'], 'Item updated.'
|
86
|
+
end
|
124
87
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class ItemsController < ApplicationController
|
2
|
+
def show
|
3
|
+
item = Item.find(params[:id])
|
4
|
+
render json: item
|
5
|
+
end
|
6
|
+
|
7
|
+
def create
|
8
|
+
@item = Item.create!(item_params)
|
9
|
+
render :show, within: '#sidebar', flash: { notice: 'Item was successfully created.' }
|
10
|
+
end
|
11
|
+
|
12
|
+
def update
|
13
|
+
item = Item.find(params[:id])
|
14
|
+
item.update_attributes!(item_params)
|
15
|
+
render nothing: true, notice: 'Item updated.'
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def item_params
|
21
|
+
params.require(:item).permit(:name)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class PostsController < ApplicationController
|
2
|
+
respond_to :html
|
3
|
+
respond_to :js, only: [:create, :update, :destroy]
|
4
|
+
|
5
|
+
def create
|
6
|
+
Post.create!(post_params)
|
7
|
+
redirect_to posts_url, notice: 'Post was successfully created.'
|
8
|
+
end
|
9
|
+
|
10
|
+
def update
|
11
|
+
post = Post.find(params[:id])
|
12
|
+
post.update_attributes!(post_params)
|
13
|
+
redirect_to post_url(post), notice: 'Post was successfully updated.'
|
14
|
+
end
|
15
|
+
|
16
|
+
def destroy
|
17
|
+
post = Post.find(params[:id])
|
18
|
+
post.destroy!
|
19
|
+
redirect_to posts_url
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def post_params
|
25
|
+
params.require(:post).permit(:title, :user_id)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class UsersController < ApplicationController
|
2
|
+
def create
|
3
|
+
user = User.new(user_params)
|
4
|
+
if user.save
|
5
|
+
flash[:notice] = 'User was successfully created.'
|
6
|
+
redirect_to user_url(user)
|
7
|
+
else
|
8
|
+
respond_to do |format|
|
9
|
+
format.html { render :new }
|
10
|
+
format.js { render_turboboost_errors_for(user) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def update
|
16
|
+
user = User.find(params[:id])
|
17
|
+
if user.update_attributes(user_params)
|
18
|
+
redirect_to user_url(user)
|
19
|
+
else
|
20
|
+
respond_to do |format|
|
21
|
+
format.html { redirect_to users_url }
|
22
|
+
format.js { render_turboboost_errors_for(user) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def user_params
|
30
|
+
params.require(:user).permit(:name, :email)
|
31
|
+
end
|
32
|
+
end
|
data/test/locales/en.yml
ADDED
data/test/test_helper.rb
CHANGED
@@ -2,31 +2,39 @@ require 'rubygems'
|
|
2
2
|
require 'bundler'
|
3
3
|
Bundler.setup
|
4
4
|
|
5
|
-
ENV[
|
5
|
+
ENV['RAILS_ENV'] = 'test'
|
6
6
|
|
7
7
|
require 'rails'
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
8
|
+
require 'active_record'
|
9
|
+
require 'action_controller'
|
10
|
+
require 'action_controller'
|
11
|
+
require 'action_controller/railtie'
|
12
12
|
require 'rails/test_help'
|
13
13
|
require 'awesome_print'
|
14
|
+
require 'responders' if Rails.version >= '4.2'
|
15
|
+
require 'strong_parameters' if Rails.version < '4.0'
|
14
16
|
require 'turboboost'
|
15
17
|
|
16
|
-
|
18
|
+
I18n.enforce_available_locales = true
|
19
|
+
I18n.load_path << File.expand_path('../locales/en.yml', __FILE__)
|
20
|
+
I18n.reload!
|
21
|
+
|
22
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
|
17
23
|
|
18
24
|
class TestApp < Rails::Application; end
|
19
25
|
Rails.application = TestApp
|
20
|
-
Rails.configuration.secret_key_base =
|
26
|
+
Rails.configuration.secret_key_base = 'abc123'
|
21
27
|
|
22
28
|
Turboboost::Routes = ActionDispatch::Routing::RouteSet.new
|
23
29
|
Turboboost::Routes.draw do
|
24
30
|
resources 'posts'
|
25
31
|
resources 'users'
|
32
|
+
resources 'items'
|
26
33
|
end
|
27
34
|
|
28
35
|
class ApplicationController < ActionController::Base
|
29
36
|
include Turboboost::Routes.url_helpers
|
37
|
+
self.view_paths = File.join(File.dirname(__FILE__), 'views')
|
30
38
|
end
|
31
39
|
|
32
40
|
class ActiveSupport::TestCase
|
@@ -36,38 +44,42 @@ class ActiveSupport::TestCase
|
|
36
44
|
end
|
37
45
|
|
38
46
|
class ActionView::TestCase
|
39
|
-
|
40
|
-
def default_url_options; {}; end
|
41
|
-
|
42
47
|
include Turboboost::Routes.url_helpers
|
43
48
|
include Turboboost::FormHelper
|
49
|
+
def default_url_options
|
50
|
+
{}
|
51
|
+
end
|
44
52
|
|
45
53
|
setup do
|
46
54
|
@controller = ApplicationController
|
47
55
|
end
|
48
|
-
|
49
56
|
end
|
50
57
|
|
51
58
|
posts_table = %{CREATE TABLE posts (id INTEGER PRIMARY KEY, title VARCHAR(5), user_id INTEGER);}
|
52
59
|
ActiveRecord::Base.connection.execute(posts_table)
|
53
60
|
|
54
61
|
class Post < ActiveRecord::Base
|
55
|
-
|
56
62
|
attr_accessor :title, :user_id
|
57
63
|
|
58
|
-
validates :title, length: { minimum: 5, message:
|
64
|
+
validates :title, length: { minimum: 5, message: 'is too short.' }
|
59
65
|
validates :user_id, presence: true
|
60
|
-
|
61
66
|
end
|
62
67
|
|
63
68
|
users_table = %{CREATE TABLE users (id INTEGER PRIMARY KEY, name VARCHAR(5), email VARCHAR(255));}
|
64
69
|
ActiveRecord::Base.connection.execute(users_table)
|
65
70
|
|
66
71
|
class User < ActiveRecord::Base
|
67
|
-
|
68
72
|
attr_accessor :name, :email
|
69
73
|
|
70
74
|
validates :name, presence: true
|
71
75
|
validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, allow_blank: false }
|
76
|
+
end
|
72
77
|
|
78
|
+
items_table = %{CREATE TABLE items (id INTEGER PRIMARY KEY, name VARCHAR(5));}
|
79
|
+
ActiveRecord::Base.connection.execute(items_table)
|
80
|
+
|
81
|
+
class Item < ActiveRecord::Base
|
82
|
+
attr_accessor :name
|
83
|
+
|
84
|
+
validates :name, presence: true
|
73
85
|
end
|