Ruby On Rails: Difference between revisions
Line 850: | Line 850: | ||
After creating the mailer the following test case was created | After creating the mailer the following test case was created | ||
require 'test_helper' | require 'test_helper' | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="ruby"> | ||
class MainMailerTest < ActionMailer::TestCase | class MainMailerTest < ActionMailer::TestCase | ||
test "notify_question_author" do | test "notify_question_author" do |
Revision as of 02:42, 16 August 2020
Installing
Install Dependencies
sudo apt update
sudo apt install -y curl gnupg2 dirmngr git-core zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev
Install Node
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt install -y nodejs
Install Yarn
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt install -y yarn
Install Ruby
Takes a while so might want to add --verbose
cd
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
exec $SHELL
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
exec $SHELL
rbenv install 2.7.1
rbenv global 2.7.1
gem install bundler
Install Rails
gem install rails
Creating a Project
This took a while but did finish on version 6.0.3.2 of rails
# Create
rails new HU
# Start the server http://localhost:3000
rails server # rails s
Basics
Creating a Controller
You pass a name and an action to create a controller
rails generate controller home index
We can not change the content of the page which is found at app/views/home/index.html.erb
<h1>Welcome</h1>
Changing the default route
Lets change the default route page to be the new page. Go app/config/routes.rb
Change from
Rails.application.routes.draw do
get 'home#index'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
Change to
Rails.application.routes.draw do
root 'home#index'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
Adding a new route
Add Route
In app/config/routes.rb add a new route by adding a get with route name and a controller and action
get '/about' => 'home#about'
Add View
You will need a new view under app/views called about.html.erb
<h1>Abouty<h1>
Add Action
You will need a new action under app/controller/home_controller.rb
class HomeController < ApplicationController
...
def about
end
end
Layouts
Adding Bootstrap To Rails
Install Packages
yarn add bootstrap jquery popper.js
Environment.js
Next, to setup Bootstrap the first thing we want to do is to go into the webpack directory’s environment.js file located at config/webpack/environment.js and add the new Provide plugin for webpack. Note that the top and bottom lines are already in the file. We just need to add the code block in the middle:
const { environment } = require("@rails/webpacker");
const webpack = require("webpack");
environment.plugins.append(
"Provide",
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
Popper: ["popper.js", "default"],
})
);
module.exports = environment;
application.js
Next we go into app/javascript/packs/application.js and under the require statements we import Bootst
import "bootstrap";
import "../stylesheets/application" // <- Add this line
document.addEventListener("turbolinks:load", () => {
$('[data-toggle="tooltip"]').tooltip()
$('[data-toggle="popover"]').popover()
})
application.scss
As a next step, we need to create a directory for our stylesheets. This file actually goes in a directory we create under app/javascript/stylesheets directory.
@import "~bootstrap/scss/bootstrap";
Add stylesheet_pack_tag
Next, in our layout file app/views/layouts/application.html.erb we need to add an additional line for the stylesheet link to include the stylesheet_pack_tag. This is going to export the JavaScipt and CSS in a single JS file (which is a bit different than the standard way) but it’s for the Webpack dev server:
<!DOCTYPE html>
<html>
<head>
<title>Bootstrapper</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
...
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track':
...
'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
Adding Bootstrap Components
We can do this by going to the bootstrap page and grab the example https://getbootstrap.com/docs/4.3/components/navbar/. The code at the time was
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Dropdown
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
Adding Bootstrap Modal
We can do this by going to the bootstrap page and grab the example https://getbootstrap.com/docs/4.3/components/modal/. The code at the time was
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
Adding Bootstrap Horizontal Form
We can do this by going to the bootstrap page and grab the example https://getbootstrap.com/docs/4.3/components/forms/. The code at the time was
<form>
<div class="form-group row">
<label for="inputEmail3" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="inputEmail3" placeholder="Email">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" placeholder="Password">
</div>
</div>
<fieldset class="form-group">
<div class="row">
<legend class="col-form-label col-sm-2 pt-0">Radios</legend>
<div class="col-sm-10">
<div class="form-check">
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios1" value="option1" checked>
<label class="form-check-label" for="gridRadios1">
First radio
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios2" value="option2">
<label class="form-check-label" for="gridRadios2">
Second radio
</label>
</div>
<input class="form-check-input" type="radio" name="gridRadios" id="gridRadios3" value="option3" disabled> <label class="form-check-label" for="gridRadios3"> Third disabled radio </label>
</fieldset>
<input class="form-check-input" type="checkbox" id="gridCheck1"> <label class="form-check-label" for="gridCheck1"> Example checkbox </label>
<button type="submit" class="btn btn-primary">Sign in</button>
</form> </syntaxhighlight>
Adding Bootstrap Media Object (4.0)
We can do this by going to the bootstrap page and grab the example https://getbootstrap.com/docs/4.0/layout/media-object/. The code at the time was
<ul class="list-unstyled">
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media my-4">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
<li class="media">
<img class="mr-3" src="..." alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">List-based media object</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</li>
</ul>
Implementing the bootstrap component into Ruby On Rails
Creating the Files
- Put the code for NavBar in a file app/views/home/_navbar.html.erb
- Put the code for Modal in a file app/views/home/_new_question_form.html.erb
- Put the code for Form in a file app/views/home/_new_question_form.html.erb by replacing the line "Modal body text goes here"
- Put the code for Media Component in app/views/home/index.html.erb
Rendering Partial Views
Putting all of this code in one file is hard to maintain. We can use the render keyword in the layout to reference a form. Slightly odd but the naming convention is _<name>.html.erb for the file and <name> for the render name. So app/views/home/_navbar.html.erb is coded as below.
<%= render 'home/navbar' %>
<%= yield %>
<%= render 'home/new_question_form' %>
Hooking up the Modal to Button
To hook the modal to a button you give the modal and id and you add Let's add data-toggle="modal" type="button" data-target="#new-question-modal" to the button.
In the dialog _new_question_form.html.erb
...
<div class="modal" tabindex="-1" role="dialog" id="new-question-modal">
...
In the _navbar.html.erb
Before
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
After
<form class="form-inline my-2 my-lg-0">
<button class="btn btn-outline-success my-2 my-sm-0" data-toggle="modal" type="button" data-target="#new-question-modal">Ask Question</button>
</form>
Replacing the form and image
For the form we need to replace it with this and remove the buttons from the dialog
<form class="form-horizontal" action="/questions" method="POST">
<div class="form-group row">
<label for="inputEmail3" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" name="email" class="form-control" id="inputEmail3" placeholder="Email" required>
</div>
</div>
<div class="form-group row">
<label for="inputQuestion" class="col-sm-2 col-form-label">Question</label>
<div class="col-sm-10">
<textarea name="question_body" type="password" class="form-control" id="inputPassword3" placeholder="What would you like to know" required></textarea>
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
And now let's replace the image on the media object with one from the tutorial.
Before
<img class="mr-3" src="..." alt="Generic placeholder image">
After
Security
Similarly to the forgery tokens ruby demands we pass a token for forms. This is achieved by replacing the form tags with the for_form. The parameters are pretty obvious
<%= form_for :question, url: '/questions', html: {'class': 'form-horizontal'} do %>
...
<% end %>
The app should currently look like
Implementing the Answer page
Set A Get Route For Questions
Edit routes.erb to add the GET operation for questions
Rails.application.routes.draw do
root 'home#index'
get '/questions' => 'home#questions'
get '/questions/:id' => 'home#questions'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
Create Page for Question Get (Answers View)
Under app/views/home create questions.html.erb
<div class="container">
<div class="lead well">
<div class="media">
<img class="mr-3" src="http://www.gravatar.com/avatar/424242" alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">iwiseman@bibble.co.nz asked:</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</div>
</div>
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<div class="media">
<img class="mr-3" src="http://www.gravatar.com/avatar/424242" alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">iwiseman@bibble.co.nz answered:</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</div>
<div class="media">
<img class="mr-3" src="http://www.gravatar.com/avatar/424242" alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1">iwiseman@bibble.co.nz answered:</h5>
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
</div>
</div>
</div>
</div>
</div>
Link out Questions to the Answers
On the main page for each question add an questions link (Answer View)
<div>
<a href="/questions/12" class="btn btn-success btn-xs">View Answers<a>
</div>
Create an Answer Modal
Copy the question modal and change the questions to answers. Add the button for Submit New Answer and the render for the modal dialog.
<div>
<button type="button" class="btn btn-success btn-sm" data-toggle="modal" data-target="#new-answer-modal">Submit New Answer</button>
<div>
<%= render 'home/new_answer_form' %>
Models
Creating a Model
Generating
We can create a model, including RESTFul routes, using the resource generator
# rails generate <table> <field name>:<data type> <field name>:<data type>
rails generate resource question email:string body:text
Fixing it after
The generator takes assumptions which may be wrong. In this example we need to change the contents for questions table to be not null
class CreateQuestions < ActiveRecord::Migration[6.0]
def change
create_table :questions do |t|
t.string :email, null: false
t.text :body, null: false
t.timestamps null: false
end
end
end
Do Migration
To create the database table run rake
rake db:migrate
rails console
Using rails console this we can populate the database and perform sql commands
# Insert using <table>.create <field:data>,<field:data>
Question.count
Question.create email: 'iwiseman@bibble.co.nz', body: 'How is the universe?'
Using the Model
Reading the data
Read Data in Controller
You can read the data in the controller into and instance variable. This will make it available to the view.
@questions = Question.all
We can sort the data by adding order
@questions = Question.order(created_at: :desc) .all
Read Data in View
We can reading data into a view by looping over the instance variable
<% @questions.each do | q| %>
<% end %>
In our case
<div class="container">
<% @questions.each do | q| %>
<div class="media">
<img class="mr-3" src="http://www.gravatar.com/avatar/424242" alt="Generic placeholder image">
<div class="media-body">
<h5 class="mt-0 mb-1"><%= q.email %></h5>
<%= q.body %>
</div>
<div>
<a href="/questions/12" class="btn btn-success btn-xs">View Answers<a>
</div>
</div>
<% end %>
</div>
Adding Accessor For Gravatar to Model
Add to the Model a function to get the gravatar for an email address
class Question < ApplicationRecord
def gravatar
"http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email)}"
end
end
Change the view
<% @questions.each do | q| %>
<div class="media">
<img class="mr-3" src="<%= q.gravatar %>" alt="Generic placeholder image">
Improving the View
Not really Ruby-on-rails but amended the view to be improved
<div class="container">
<% @questions.each do |q| %>
<div class="media">
<img class="mr-3" src="<%= q.gravatar %>">
<div class="media-body">
<h4 class="mt-0"><%= q.email %> asked:</h4>
<div><%= time_ago_in_words q.created_at %> ago</div>
<%= q.body %>
</div>
</div>
<% end %>
</div>
Adding a Question (PUT)
Form Data
We need to change the form to return an array of data instead of the individual items. We can do this by changing the input modal to use an array. e.g.
<div class="form-group row">
<label for="inputQuestion" class="col-sm-2 col-form-label">Question</label>
<div class="col-sm-10">
<textarea name="question[question_body]" type="password" class="form-control" id="inputPassword3" placeholder="What would you like to know" required></textarea>
</div>
</div>
Create Action On Controller
Next, on the question controller we need a create method. It is important that the names all line up. IE. the names on the modal coincide with the database names. The purpose of the private function is to ensure that only the named parameters can be passed.
class QuestionsController < ApplicationController
def create
Question.create(question_params)
redirect_to root_path
end
private
def question_params
params.require(:question).permit(:email, :body)
end
end
Show Answer (GET)
Modify Quest List to pass Question ID
Creating a resourceful route will also expose a number of helpers to the controllers in your application. In the case of resources :photos
- photos_path returns /photos
- new_photo_path returns /photos/new
- edit_photo_path(:id) returns /photos/:id/edit (for instance, edit_photo_path(10) returns /photos/10/edit)
- photo_path(:id) returns /photos/:id (for instance, photo_path(10) returns /photos/10)
In the index page change the question item to pass the id.
<div class="container">
<% @questions.each do |q| %>
<div class="media">
<img class="mr-3" src="<%= q.gravatar %>">
<div class="media-body">
<h4 class="mt-0"><%= q.email %> asked:</h4>
<div><%= time_ago_in_words q.created_at %> ago</div>
<%= q.body %>
<div>
<a href="<%= question_path(q) %>" class="btn btn-success btn-xs">View Answers</a>
...
Creating Show Action
Next we need to add a show method to the question controller and retrieve the question using the parameters passed.
class QuestionsController < ApplicationController
...
def show
@question = Question.find(params[:id])
end
Modify Questions Details
The original details page was called questions.erb.html. Let move this to the app/views/questions/show.html.erb and remove the hard coded question data.
<div class="container">
<div class="lead well">
<div class="media">
<img class="mr-3" src="<%= @question.gravatar %>">
<div class="media-body">
<h4 class="mt-0"><%= @question.email %> asked:</h4>
<div><%= time_ago_in_words @question.created_at %> ago</div>
<%= @question.body %>
</div>
</div>
</div>
...
Implementing Answer
Create the Resource
Piece of cake, use make sure you include the foreign key to question
# rails generate <table> <field name>:<data type> <field name>:<data type>
rails generate resource answer question_id:integer email:string body:text
Do Migration And Populate
Migrate and populate the database
rake db:migrate
Add some records using rails console
Answer.create question_id: 1, email: 'samer@on-site.com', body: 'Answer 1'
Answer.create question_id: 1, email: 'samer@on-site.com', body: 'Answer 2'
Modify Create Dialog
We have to modify the dialog to put the id of the question into the post. The easiest way is to add a hidden field
<input type="hidden" name="answer[question_id]" value="<%= @question_id %>">
We need to make the parameters on the create dialog to be an array.
...
<div class="modal-body">
<%= form_for :answer, url: '/answers', html: {'class': 'form-horizontal'} do %>
<input type="hidden" name="answer[question_id]" value="<%= @question.id %>">
<div class="form-group row">
<label for="inputEmail3" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" name="answer[email]" class="form-control" id="inputEmail3" placeholder="Email" required>
</div>
</div>
<div class="form-group row">
<label for="inputAnswer" class="col-sm-2 col-form-label">Answer</label>
<div class="col-sm-10">
<textarea name="answer[body]" type="password" class="form-control" id="inputPassword3" placeholder="What would you like to know" required></textarea>
</div>
</div>
...
Relationships In Rails
Introduction
These, as you might expect, are defined on the model. With Question and Answer we have a one to many or many or one depending on which entity is on the right.
Types of Association
Rails supports six types of associations:
- belongs_to
- has_one
- has_many
- has_many :through
- has_one :through
- has_and_belongs_to_many
Answer/Question Example
We can define these in the models as below.
class Answer < ApplicationRecord
belongs_to :question
end
class Question < ApplicationRecord
has_many :answers
def gravatar
"http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(email)}"
end
end
Other options are
Add Create Method
We can now get the question from the Question collection by retrieving the question id from the answer passed.
This allows us to
- create a record on the answers collection rather than explicit question_id
- redirect to redirect to the question.
class AnswersController < ApplicationController
def create
question = Question.find(params[:answer][:question_id])
question.answers.create(answer_params)
redirect_to question
end
def answer_params
params.require(:answer).permit(:email, :body)
end
end
Modify Display Answers
Because of the relationship and the instance variable question we are now able to reference the answers associated with the question
<div class="container">
<div class="card card-body bg-light">
<div class="media">
<img class="mr-3" src="<%= @question.gravatar %>">
<div class="media-body">
<h4 class="mt-0"><%= @question.email %> asked:</h4>
<div><%= time_ago_in_words @question.created_at %> ago</div>
<%= @question.body %>
<% @question.answers.each do |a| %>
<div class="media">
<img class="mr-3" src="<%= a.gravatar %>">
<div class="media-body">
<h4 class="mt-0"><%= a.email %> answered:</h4>
<div><%= time_ago_in_words a.created_at %> ago</div>
<%= a.body %>
</div>
</div>
<% end %>
<div>
<button type="button" class="btn btn-success btn-sm" data-toggle="modal" data-target="#new-answer-modal">Submit New Answer</button>
<div>
</div>
</div>
</div>
<%= render 'home/new_answer_form' %>
</div>
Enhancing the GUI
if statements in View
We can perform if statements like this in ruby on rails.
<% if @questions.empty? %>
<div class="alert alert-info">There are not questions at this time</div>
<% end %>
Session Dictionary
We can store data in the session dictionary in ruby. This can later be retrieved by the view. For example
session[:current_user_email] = question_params[:email]
Helper Functions
Under helpers we can create functions to be share either across the application or within a view. In our case we are going to share the current_user_email function
module ApplicationHelper
def current_user_email
session[:current_user_email]
end
end
Now we can change the modal to get the value from the helper function.
<div class="modal-body">
<%= form_for :answer, url: '/answers', html: {'class': 'form-horizontal'} do %>
<input type="hidden" name="answer[question_id]" value="<%= @question.id %>">
<div class="form-group row">
<label for="inputEmail3" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="email" name="answer[email]" value="<%= current_user_email %>" class="form-control" id="inputEmail3" placeholder="Email" required>
</div>
</div>
Testing in Ruby on Rails
Generate a mailer
A mailer is a model/view/controller for a mailer. We can generate this by using the rails generator and passing name of the mailer and a list of methods for each function you want.
rails generate mailer main_mailer notify_question_author
Test Cases
After creating the mailer the following test case was created require 'test_helper'
class MainMailerTest < ActionMailer::TestCase
test "notify_question_author" do
mail = MainMailer.notify_question_author
assert_equal "Notify question author", mail.subject
assert_equal ["to@example.org"], mail.to
assert_equal ["from@example.com"], mail.from
assert_match "Hi", mail.body.encoded
end
end