crud_actions 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7189f145107ce7b831b50d2f4806433e88871a41
4
+ data.tar.gz: bc0a2d60e755efedb7461db320594b1bef51e6bf
5
+ SHA512:
6
+ metadata.gz: 14c9b8fc46caa6c9fb3c09e8d1bc24962ea36175963ecc253a68dffdfd06024ca9dc288a16f62d366d534bf739d6442da11e8d93ab6c9d083f2d611bc5b41d64
7
+ data.tar.gz: 9343d86db664812008df573054af3b36b7580f4e37f893a481c1feaa7d6841e0c4f0c27a23759f02ddd7258dc5310f56c019632d6103f66a5853893e7eb06807
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Amit Choudhary
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,128 @@
1
+ = CrudActions
2
+
3
+ CrudActions provides all the seven crud actions(i.e - index, new, create, edit, update, show and destroy) to your controller with basic functionalities.
4
+
5
+ = Installation
6
+ Write following in your gemfile and bundle install : -
7
+ * gem 'crud_actions'
8
+
9
+ = Usage
10
+ Define a method named as 'has_inherited_actions' with arguments as action names which you want to include from the gem.
11
+ For example: -
12
+
13
+ Class CategoriesController < ActionController::Base
14
+ has_inherited_actions :index, :new, :create, :edit, :update, :show, :destroy
15
+ end
16
+
17
+ CategoriesController will have now all the seven actions which are passed as arguments to the 'has_inherited_actions'.
18
+
19
+ The action names which are not passed as arguments are not defined for the controller and need to be defined separately in the controller
20
+
21
+ = index action usage
22
+ index action will be defined like :-
23
+ def index
24
+ @categories = Category.all
25
+ end
26
+ You can also pass some options to the index - 'includes' and 'order'. Like if you define your controller like this: -
27
+ class CategoriesController < ActionController:: Base
28
+ has_inherited_actions :index
29
+ def index
30
+ super({ includes: :subcategories, order: :name })
31
+ end
32
+ end
33
+ Then the action will be defined as
34
+ def index
35
+ @categories = Category.includes(:subcategories).order(:name)
36
+ end
37
+
38
+ = new action usage
39
+ new action will be defined like : -
40
+ def new
41
+ @category = Category.new
42
+ end
43
+
44
+ = create action usage
45
+ create action expects controller to define a method named as permitted_params_for_create for permitting params.
46
+ create action will be defined like :-
47
+ def create
48
+ @category = Category.new(permitted_params_for_create)
49
+ if @category.save
50
+ flash[:notice] = 'Category created successfully.'
51
+ redirect_to action: :index
52
+ else
53
+ flash[:alert] = @category.errors.full_messages.join(', ')
54
+ redirect_to action: :new
55
+ end
56
+ end
57
+
58
+ You can also pass some options to the create - 'redirect_to', 'notice' and 'alert'. Like if you define your controller like this: -
59
+ class CategoriesController < ActionController:: Base
60
+ has_inherited_actions :create
61
+ def create
62
+ super({ redirect_to: subcategories_path, notice: 'Successful creation', alert: 'Could not create.' })
63
+ end
64
+ end
65
+ 'redirect_to' option will be used to redirect to a path after successful creation.
66
+ 'success' option will be used to set flash[:notice] instead of default message.
67
+ 'alert' option will be used to set flash[:alert] instead of default message.
68
+
69
+ = edit action usage
70
+ edit action will be defined like : -
71
+ def edit
72
+ end
73
+
74
+ = update action usage
75
+ update action expects controller to define a method named as permitted_params_for_update for permitting params.
76
+ It also expects controller to define an instance variable named properly before update(like: - @category in case of CategoriesController, @exams_question in case of ExamsQuestionController)
77
+ update action will be defined like :-
78
+ def update
79
+ if @category.update(permitted_params_for_update)
80
+ flash[:notice] = 'Category updated successfully.'
81
+ redirect_to action: :index
82
+ else
83
+ flash[:alert] = @category.errors.full_messages.join(', ')
84
+ redirect_to action: :edit
85
+ end
86
+ end
87
+
88
+ You can also pass some options to the update - 'redirect_to', 'notice' and 'alert'. Like if you define your controller like this: -
89
+ class CategoriesController < ActionController:: Base
90
+ has_inherited_actions :update
91
+ def update
92
+ super({ redirect_to: subcategories_path, notice: 'Successful updation.', alert: 'Could not update.' })
93
+ end
94
+ end
95
+ 'redirect_to' option will be used to redirect to a path after successful creation.
96
+ 'success' option will be used to set flash[:notice] instead of default message.
97
+ 'alert' option will be used to set flash[:alert] instead of default message.
98
+
99
+ = show action usage
100
+ show action will be defined like : -
101
+ def show
102
+ end
103
+
104
+ = destroy action usage
105
+ destroy action expects controller to define an instance variable named properly before destroy(like: - @category in case of CategoriesController, @exams_question in case of ExamsQuestionController)
106
+ destroy action will be defined like :-
107
+ def destroy
108
+ if @category.destroy
109
+ flash[:notice] = 'Category destroyed successfully.'
110
+ else
111
+ flash[:alert] = @category.errors.full_messages.join(', ')
112
+ end
113
+ redirect_to action: :index
114
+ end
115
+
116
+ You can also pass some options to the destroy - 'redirect_to', 'notice' and 'alert'. Like if you define your controller like this: -
117
+ class CategoriesController < ActionController:: Base
118
+ has_inherited_actions :destroy
119
+ def destroy
120
+ super({ redirect_to: subcategories_path, notice: 'Successful deletion.', alert: 'Could not destroy.' })
121
+ end
122
+ end
123
+ 'redirect_to' option will be used to redirect to a path.
124
+ 'success' option will be used to set flash[:notice] instead of default message.
125
+ 'alert' option will be used to set flash[:alert] instead of default message.
126
+
127
+
128
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'CrudActions'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,81 @@
1
+ require 'crud_actions/base_helper'
2
+ require 'crud_actions/actions_helper'
3
+
4
+ module CrudActions
5
+ module Actions
6
+
7
+ include ::CrudActions::BaseHelper
8
+ include ::CrudActions::ActionsHelper
9
+ extend ActiveSupport::Concern
10
+
11
+ DEFAULT_ACTIONS = [:index, :new, :create, :edit, :update, :show, :destroy]
12
+
13
+ def self.included(class_name)
14
+ model_name = class_name.to_s.split('Controller').first.split('::').last.singularize
15
+ class_name.instance_variable_set('@resource_class', model_name.constantize)
16
+ class_name.instance_variable_set('@resource_name', model_name.underscore)
17
+ end
18
+
19
+ module ClassMethods
20
+ def has_inherited_actions(*actions)
21
+ excluded_actions = DEFAULT_ACTIONS - actions
22
+ self.send(:undef_method, *excluded_actions)
23
+ end
24
+ end
25
+
26
+ def index(options = { order: nil, includes: nil })
27
+ index_helper(controller_name, options)
28
+ end
29
+
30
+ def new
31
+ resource_name = self.class.instance_variable_get('@resource_name')
32
+ instance_variable_set("@#{ resource_name }", self.class.instance_variable_get('@resource_class').new)
33
+ end
34
+
35
+ def create(options = { notice: nil, alert: nil })
36
+ resource_name = self.class.instance_variable_get('@resource_name')
37
+ resource_class = self.class.instance_variable_get('@resource_class')
38
+ instance_variable_set("@#{ resource_name }", resource_class.new(permitted_params_for_create))
39
+ resource = instance_variable_get("@#{ resource_name }")
40
+ if resource.public_send(:save)
41
+ flash[:notice] = options[:notice] || "#{ resource_class.to_s } created successfully."
42
+ redirect_to (options[:redirect_to] || path_maker)
43
+ else
44
+ flash[:alert] = options[:alert] || resource.errors.full_messages.join(', ')
45
+ render action: :new
46
+ end
47
+ end
48
+
49
+ def edit
50
+ end
51
+
52
+ def show
53
+ end
54
+
55
+ def update(options = { notice: nil, alert: nil })
56
+ resource_name = self.class.instance_variable_get('@resource_name')
57
+ resource_class = self.class.instance_variable_get('@resource_class')
58
+ resource = instance_variable_get("@#{ resource_name }")
59
+ if resource.public_send(:update, permitted_params_for_update)
60
+ flash[:notice] = options[:notice] || "#{ resource_class.to_s } updated successfully."
61
+ redirect_to (options[:redirect_to] || path_maker)
62
+ else
63
+ flash[:alert] = options[:alert] || resource.errors.full_messages.join(', ')
64
+ render action: :edit
65
+ end
66
+ end
67
+
68
+ def destroy(options = { notice: nil, alert: nil })
69
+ resource_name = self.class.instance_variable_get('@resource_name')
70
+ resource_class = self.class.instance_variable_get('@resource_class')
71
+ resource = instance_variable_get("@#{ resource_name }")
72
+ if resource.public_send(:destroy)
73
+ flash[:notice] = options[:notice] || "#{ resource_class.to_s } destroyed successfully."
74
+ else
75
+ flash[:alert] = options[:alert] || resource.errors.full_messages.join(', ')
76
+ end
77
+ redirect_to (options[:redirect_to] || path_maker)
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,9 @@
1
+ module CrudActions
2
+ module ActionsHelper
3
+
4
+ def index_helper(resource_name, options)
5
+ instance_variable_set("@#{ resource_name.pluralize }", self.class.instance_variable_get('@resource_class').includes(options[:includes]).order(options[:order]))
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module CrudActions
2
+ module BaseHelper
3
+
4
+ private
5
+ def path_maker
6
+ { action: :index, controller: controller_name }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module CrudActions
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'crud_actions/actions'
2
+
3
+ module CrudActions
4
+ end
@@ -0,0 +1,144 @@
1
+ require 'test_helper'
2
+
3
+ class CrudActionsTest < ActiveSupport::TestCase
4
+ test "truth" do
5
+ assert_kind_of Module, CrudActions
6
+ end
7
+ end
8
+
9
+ class Category < ActiveRecord::Base
10
+ end
11
+
12
+ class CategoriesController < ActionController::Base
13
+ include CrudActions::Actions
14
+ has_inherited_actions :index, :new, :create, :show, :destroy, :update, :edit
15
+
16
+ def permitted_params_for_create
17
+ params.require(:category).permit!
18
+ end
19
+
20
+ def permitted_params_for_update
21
+ params.require(:category).permit!
22
+ end
23
+ end
24
+
25
+
26
+ class CategoriesControllerTest < ActionController::TestCase
27
+
28
+ def setup
29
+ @controller = CategoriesController.new
30
+ @request = ActionController::TestRequest.new
31
+ @response = ActionController::TestResponse.new
32
+ end
33
+
34
+ test 'controller should have an index action' do
35
+ get :index
36
+ assert_response :success
37
+ end
38
+
39
+ test 'an instance variable should be assigned inside index' do
40
+ get :index
41
+ assert assigns(:categories)
42
+ end
43
+
44
+ test 'index action should render index page' do
45
+ get :index
46
+ assert_template :index
47
+ end
48
+
49
+ test 'controller should have an edit action and render edit template' do
50
+ get :edit, {id: 2}
51
+ assert_template :edit
52
+ end
53
+
54
+ test 'controller should have an update action and renders edit page on erroneous submission' do
55
+ @controller.instance_variable_set('@category', Category.new({id: '1', text: 'Crud Actions'}))
56
+ @controller.instance_variable_get('@category').expects(:update).returns(false)
57
+ patch :update, ({id: 1, category: {text: 'New Crud Actions'}})
58
+ assert_template :edit
59
+ assert_match @controller.flash[:notice].to_s, ''
60
+ end
61
+
62
+ test 'controller should have update method and redirects on successful submission' do
63
+ @controller.instance_variable_set('@category', Category.new({id: '1', text: 'Crud Actions'}))
64
+ @controller.instance_variable_get('@category').expects(:update).returns(true)
65
+ patch :update, ({id: 1, category: {text: 'New Crud Actions'}})
66
+ assert_redirected_to categories_path
67
+ assert_match @controller.flash[:notice], 'Category updated successfully.'
68
+ end
69
+
70
+ test 'controller should have a new action and assigns an instance variable' do
71
+ get :new
72
+ assert_response :success
73
+ assert_equal 'New HTML', @response.body.strip
74
+ assert assigns(:category)
75
+ end
76
+
77
+ test 'controller should have a create action and redirects on successful submission' do
78
+ Category.any_instance.stubs(:save).returns(true)
79
+ put :create, category: {text: 'Crud Actions'}
80
+ assert assigns(:category)
81
+ assert_redirected_to categories_path
82
+ assert_match @controller.flash[:notice], 'Category created successfully.'
83
+ end
84
+
85
+ test 'controller should have a create action and renders new page on erroneous submission' do
86
+ Category.any_instance.stubs(:save).returns(false)
87
+ get :create, category: {text: 'Crud Actions'}
88
+ assert assigns(:category)
89
+ assert_response :success
90
+ assert_template :new
91
+ end
92
+
93
+ test 'controller should have a show action' do
94
+ get :show, {id: '2'}
95
+ assert_template :show
96
+ end
97
+
98
+ test 'controller should have a destroy action and shows flash[:notice] for successful submission' do
99
+ @controller.instance_variable_set('@category', Category.new({id: '1', text: 'Crud Actions'}))
100
+ @controller.instance_variable_get('@category').expects(:destroy).returns(true)
101
+ delete :destroy, {id: '2'}
102
+ assert_redirected_to categories_path
103
+ assert_match @controller.flash[:notice], 'Category destroyed successfully.'
104
+ end
105
+
106
+ test 'controller should have a destroy method and shows flash[:errors] for erroneous submission' do
107
+ @controller.instance_variable_set('@category', Category.new({id: '1', text: 'Crud Actions'}))
108
+ @controller.instance_variable_get('@category').expects(:destroy).returns(false)
109
+ delete :destroy, {id: '2'}
110
+ assert_redirected_to categories_path
111
+ assert_match @controller.flash[:notice].to_s, ''
112
+ end
113
+
114
+ end
115
+
116
+ class Product < ActiveRecord::Base
117
+ end
118
+
119
+ class ProductsController < ActionController::Base
120
+ include CrudActions::Actions
121
+ has_inherited_actions :index, :new, :create, :show, :destroy
122
+
123
+ def permitted_params_for_update
124
+ params.require(:category).permit!
125
+ end
126
+ end
127
+
128
+ class ProductsControllerTest < ActionController::TestCase
129
+ test 'controller should raise exception when action not defined' do
130
+ begin
131
+ patch :update, ({id: 1, product: {name: 'New Crud Actions'}})
132
+ rescue Exception => e
133
+ assert_match "The action 'update' could not be found for ProductsController", e.message
134
+ end
135
+ end
136
+
137
+ test 'should indicate absence of "permitted_params_for_create" for create action when not defined' do
138
+ begin
139
+ put :create
140
+ rescue Exception => e
141
+ assert_match /undefined local variable or method `permitted_params_for_create'/, e.message
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,52 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require 'minitest/autorun'
5
+ require 'mocha/mini_test'
6
+ require 'minitest/rg'
7
+ require "active_support"
8
+ require "active_record"
9
+ require "action_controller"
10
+ require 'crud_actions'
11
+
12
+ # Filter out Minitest backtrace while allowing backtrace from other libraries
13
+ # to be shown.
14
+ Minitest.backtrace_filter = Minitest::BacktraceFilter.new
15
+
16
+ #Establish connection to test instance creation for models
17
+
18
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
19
+
20
+ ActiveRecord::Schema.define do
21
+ self.verbose = false
22
+
23
+ create_table :categories, :force => true do |t|
24
+ t.string :text
25
+ end
26
+
27
+ create_table :products, :force => true do |t|
28
+ t.string :name
29
+ end
30
+ end
31
+
32
+ # Load support files
33
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
34
+
35
+ ActionController::Base.view_paths = File.join(File.dirname(__FILE__), 'views')
36
+
37
+ #Create Routes to test assertions on routings
38
+
39
+ CrudActions::Routes = ActionDispatch::Routing::RouteSet.new
40
+
41
+ CrudActions::Routes.draw do
42
+ resources :categories
43
+ resources :products
44
+ end
45
+
46
+ ActionController::Base.send :include, CrudActions::Routes.url_helpers
47
+
48
+ class ActionController::TestCase
49
+ setup do
50
+ @routes = CrudActions::Routes
51
+ end
52
+ end
@@ -0,0 +1 @@
1
+ Edit HTML
@@ -0,0 +1 @@
1
+ This is index page
@@ -0,0 +1 @@
1
+ New HTML
@@ -0,0 +1 @@
1
+ Show HTML
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crud_actions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Amit Choudhary
8
+ - Shruti Gupta
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-10-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 4.2.4
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 4.2.4
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 4.2.4
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 4.2.4
42
+ - !ruby/object:Gem::Dependency
43
+ name: actionpack
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 4.2.4
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 4.2.4
56
+ - !ruby/object:Gem::Dependency
57
+ name: sqlite3
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 1.3.10
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 1.3.10
70
+ description: A method named as has_inherited_actions is added which receives action
71
+ names as paremeters which need to be defined on controller.
72
+ email:
73
+ - amitchoudhary1008@gmail.com
74
+ - inklingviashruti@gmail.com
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - MIT-LICENSE
80
+ - README.rdoc
81
+ - Rakefile
82
+ - lib/crud_actions.rb
83
+ - lib/crud_actions/actions.rb
84
+ - lib/crud_actions/actions_helper.rb
85
+ - lib/crud_actions/base_helper.rb
86
+ - lib/crud_actions/version.rb
87
+ - test/crud_actions_test.rb
88
+ - test/test_helper.rb
89
+ - test/views/categories/edit.html.erb
90
+ - test/views/categories/index.html.erb
91
+ - test/views/categories/new.html.erb
92
+ - test/views/categories/show.html.erb
93
+ homepage: https://github.com/amit-vinsol/crud_actions
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.4.6
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: CrudActions gem provides seven crud actions with basic definitions.
117
+ test_files:
118
+ - test/crud_actions_test.rb
119
+ - test/test_helper.rb
120
+ - test/views/categories/edit.html.erb
121
+ - test/views/categories/index.html.erb
122
+ - test/views/categories/new.html.erb
123
+ - test/views/categories/show.html.erb