Skip to content

Instantly share code, notes, and snippets.

@s-taylor
Last active August 29, 2015 13:57
Show Gist options
  • Save s-taylor/9610045 to your computer and use it in GitHub Desktop.
Save s-taylor/9610045 to your computer and use it in GitHub Desktop.
Rails Notes

Current as at 18-MAR-2014

#Rails Notes


###HTTP Requests###

Contains… GET

  • domain
  • path

###Controller

When an HTTP request hits a rails app, it will first hit the dispatcher. This will look at the path it was given and map this path to an action, how the request will be responded to.

This will be stored inside routes.rb

get '/' => 'home#index'

This will then route this request to the Controller home = controller (class) index = action (method) In doing so it will create an instance of the home class!

In your rails app you will therefore have a file called…
home_controller.rb (must end in _controller) In this file you will have a class called HomeController and a method called index

class HomeController < ApplicationController
    def index
        your code here
    end
end

Note: This is all based on naming conventions!!!

###View The simplest code you could put inside this method (index) is to render a webpage.

def index
    render 'index'
end

This will then return the index.html.erb file. ERB are essentially html files where you can embed ruby code. Also known as templates.

The render method is inherited from the ApplicationController which inherits from the ActionController::Base

Using the above example where routed to 'home#index' by default it will look for views in: /views/home/index.html.erb

###Model This is where you would store all of your business logic code, the functionality.

Your classes in this code may interact with a database using ActiveRecord::Base

###Rails ####Creating a new Rails App#### Rails new yyy - create a new rails project named yyy. This will create a folder called yyy with sub folders. This is effectively a template for a new ruby app.

####Files and Folders in the App#### Gemfile contains all the ruby Gems you wish to use in your project.

Most of the code you will write should go in the /app folder. In this there are…
/assets - for you stylesheets and javascript
/controller - for your controllers
/helpers - to store various helper methods
/mailers - to send emails to your website users
/models - for your models
/views - for your views

Other folders are...
/bin - some command line tools you may use
/config - ?
/db - for your database code
/lib - ?
/log - where log files are stored. All requests through the rails stack write to a log file.
/public - ?
/test - for test driven development to store your test code
/temp - caching, no need to touch
/vendor - for code that you're using by other vendors that you are not modifying and is not a gem (e.g. jQuery, bootstrap)

####Launch the Rails Server#### In terminal type rails server OR rails s for short

access your server via "localhost:3000" in your browser

####Rails Coding#### In the routes.rb file…

get '/' => 'home#index'

Create a file called home_controller.rb in the /app/controllers folder. Code …

class HomeController < ActionController
	def index
    	your code
	end
end

Note: The default behaviour if we input nothing in the controller (no index or other methods) will be for the server to return a index.html.erb file in the app/views/home folder

In the index.html.erb file you do not need to include all the standard code (e.g. html, head, body tags…). This is automatically applied using the app/views/layouts/application.html.erb file and will insert your code inside the <%= yield %> section.

####Scaffold#### rails generate scaffold books title:string author:string published_on:date

This will generate all ruby code needed to handle 'books'.

Note: This usually will generate far more code than we need and is not generally recommended to use scaffolds

rake db:migrate will create the database changes needed.

###Code Examples ####Create a New Rails App
rails new xxxx

####Setup Routes

  • navigate to /config/routes.rb
  • input get '/' => 'home#index' OR root 'home#index' for short

We can also setup a named route by adding an , :as => 'secret_number'
e.g. get '/secret_number' => 'secret_number#index', :as => 'secret_number' This creates two methods for us which return two strings...

secret_number_path - '/secret_number'
secret_number_url - 'http://localhost:3000/secret_number'

Note: Route orders do matter! The dispatcher will search for a matching route from top to bottom. Therefore if you want movies/search, this needs to be before the show method generated by resources as this will look for movies/:id and 'search' will become the id parameter.

#####Parameters in Routes Wherever we use a : in our routes definition this will be available in our params hash.

get '/guess/:guess' => 'secret_number#guess', as: => 'secret_number'

Then in our erb file we can use…

<%= link_to "Number #{num}", guess_path(num)%>

Note: In this case 'num' was our loop variable (defined in the block).

This means when we use the guess_path, we feed the num value via the url and this will then be available inside a hash called params[:guess]

All parameters will be strings regardless of what they were when passed so can need conversion!


###Setup MVC (Model, View, Controller)

####Create Controller

  • navigate to /app/controllers
  • create file home_controller.rb
  • input…
class HomeController < ApplicationController
    your code here  
end
  • Then you can input the index action (method)
def index  
    do something  
end
  • Inside the index action (method) we can input render "index"
    But… this is not necessary, as by default it will look for a template that matches the action name. You could even not write the method entirely.

#####Before Action This allows us to run code prior to running the controller method.

Syntax = before_action :method_name and put this at the top of your controller.rb

It is good practice to setup the Before Action method as protected. This indicates that it is never going to be called via a route. To do this put "protected" above the method. All methods below this point will not work via a route.

####Create a View we then need to create a view (erb)

  • navigate to /app/views
  • create a folder called home
  • inside this folder create a file called index.html.erb
  • put your html code inside the index.html.erb
  • to insert ruby code we can input <%= xxx %> to insert ruby code into our html. This could be a variable or method result. Alternatively if you just wanted the code to run but not display in the template just put <% xxx %>

#####Partials (inserting .erb inside .erb) This allows you to use templates within templates and avoid repeating html code in your erb files(partials).
You can access the 'partial' by using the render function within erb file. Any time render is used inside an erb it will look for the appropriate partial file.
The partial file must start with an underscore ( _ ) this is how it recognises it as a partial vs a standard erb.
e.g. _guesses.html.erb

######Rendering with Objects

You can use the Render function such that the fields from your object are available inside the view without a loop.
<%= render @classifieds %>

This will look for a the view located...
app/views/classifieds/_classified.html.erb
This view will then render the view once for each object in @classifieds.

This can then also be used on your show method if it is the same using
<%= render @classified %>

####Create a Model

  • from terminal type rails g model [model_name] [column:type column:type…]
    type can be: :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean)
    Note: model names should not be plural! This will confuse it with the table name.
  • from terminal type rake db:migrate (this will run the ruby migration code) this will create the model file (e.g. Movie, note the capitalisation), the database table (movies, note it is plural and no caps!) and fields.
  • this will have created a model file in /app/controllers/model.rb and this will inherit from ActiveRecord::Base

e.g. rails g model shirt name:string description:string image:string
this will create a file with ruby code to generate our database table. you can view this code in db/migrate/xxxx_create_shirts.rb It is a loop that will inherit from ActiveRecord::Migration and create a table and all required fields in addition it will create two fields created_at and updated_at

###Model Methods In console, you can reference the object itself without a method to list the fields in the table these methods are all class methods (can be called without instansiating the object). In most cases these methods will return an instance of the object itself.
.all - returns all records in our database (as an array of objects)
.find(x) - return the record with the primary key = x
.create(hash with values) - create a new record in the database immediately
.new(hash with values) - create a mew record to be saved in the database
.save - save the 'new' instance of the object (after using .new)
.any? - checks to see if records exist (returns true/false)

.where(:key => value) - restricts the records returned as specified
.where("field like :something", :something => my_search)
e.g.

@shirts = Shirt.where("name like :query", :query => "%#{params[:query]}%")

Note: Do not use string interpolation for where clause, this opens you up to SQL injection attacks which are protected when using the above syntax.

###Model Changes First generate a migration (see below)

rails g migration xxxxx (specify the name of the .rb file you want to create)

Then edit this .rb file in the /db folder
Note: when refering the below commands require table names! (the plural version) and not your model name.

####To add a column to an existing table add_column :table_name, :field_name, :type
e.g. add_column :shirts, :size, :string Note: Beware you must input the table name which is plural and not the model name!!!

####Remove a column from a table remove_column :table_name, :field_name, :type
e.g. remove_column :products, :part_number, :string

####Rollback a Migration rake db:rollback STEP=X
where X = the number of migrations you want to undo, 1, 2, 3 etc...

####Destroy a Migration rails destroy migration xxx (where xxx is the migration name)
This will remove the migration from being tracked by your app

###Helper Methods <%= link_to [text],[url] %>
This is best used in conjunction with named routes ,:as => alias
e.g. <%= link_to [text], alias_path %>

You can request a link to open in a new window using…
<%= link_to [text],[url],:target => "_blank" %>

You can pass a parameter in your link to use in the next page e.g.
<%= render 'list', :show_edit_links => false %>
Then in your view...

<% if show_edit_links %>
	your code
<% end %>

redirect_to [url]
This will redirect you to the specified url. This can be used in conjunction with your route paths e.g. redirect_to home_path
Note: if you redirect to an object, by default this will try redirect to the 'show path'.

###Logging /log/development.log
Rails.logger.debug("message") - This will only print in your dev environment and not prod
Other levels of messages (instead of debug) are…
.info("message") - will not be printed in prod
.warn("message") - will be printed in prod
.error("message") - will be printed in prod

###Rails Terminal Commands to list all rails commands type...
rails

to launch rails console (to interact with your model)
rails console (must be in your app directory)

from console you can type
reload! to refresh the console i.e. if you've made table changes.

to purge all records from your database
rake db:reset

to run your seeds file and populate the database
rake db:seed

###Rake Terminal Commands to list all rake commands type…
rake -T
Note: Capital T!

find the status of my migrations
from terminal type rake db:migrate:status

undo a db migration
from terminal rake db:migrate:down VERSION=xxxxx

purge all records from database
rake db:drop db:create db:migrate

###CRUD (Create, Read, Update, Delete) ####Creating a search search for "form_tag" on http://api.rubyonrails.org/ this explains how to code the form search for "text_field_tag" - this explains how to create the text field search for "submit_tag" - this explains how to create the search button

e.g.

<%= form_tag search_path do %> 
  <%= text_field_tag 'query' %> 
  <%= submit_tag 'search' %>  
<% end %>  
  • where 'search_path' is where the post request will be made to
  • where 'query' is our search term
  • where 'search' is our button name

Clicking 'search' will submit a post request with the params[:query] as part of the http request. We would need to setup a route similar to…
post '/shirts/search' => 'shirts#search', :as => 'search'
Note: this is a 'post' request not a 'get' request.

####New / Create In the 'new' method @shirt = Shirt.new

This is needed so when we use the "form_for" method in building our view as this finds the attributes of the Shirt class.

In our 'new' view

<%= form_for @shirt do |f| %>  
    <div>
        <%= f.label :name %>  
        <%= f.text_field :name %>
    </div>
    <div>
        <%= f.label :description %>
        <%= f.text_area :description %>  
    </div>  
    <%= f.submit %>  
<% end %>

using |f| is recommended as this block is for the form not the shirt object.

upon submission this will create a key in the params hash called 'shirt'
params[:shirt] == {:name => form_input, :desription => :form_input2}

#####Collection Select You can also use a collection select in your form helper Paramaters are...

  • Which field to save this selection in upon create
  • What list of objects to display (a query)
  • What field from our query to save in our object
  • What field to display in the dropdown for selection

<%= f.collection_select :category_id, Category.all, :id, :name, prompt: true %>

In the 'create' method

@shirt = Shirt.new(params.require(:shirt).permit(:name, :description))

This means a shirt object must be passed as a parameter and this object must have instance variables of "name" and "description". It is good practice to restrict which fields can be updated as this ensures the POST request cannot be modified to updated fields you do not wish to be updated.

Then we want to save the shirt, but if it's

if @shirt.save
	redirect_to '/' #redirect to back a path (i.e. if at /shirts/new, takes to /shirts)
else
	render 'new' #take me back to new, this will keep the parameters in the fields
end

In the MODEL, we can then add validations…

validates :name, :presence => true

This requres the name to be populated, if not an error is created

validates :year, :numericality => true

This requires year to be a number

These errors can be output in our html using…

<% if @shirt.errors.any? %>
	<p>There were problems saving your shirt:</p>
	<ul>
		<% @shirt.errors.full_messages.each do |message| %>
			<li><%= message %></li>
   		<% end %>
	</ul>
<% end %>

####Edit / Update Create hyperlink to edit page <%= link_to "edit", edit_shirt_path(shirt) %> You would use this in a loop that lists your shirts so you can pass in the "shirt" into the method. By default referring to the shirt will return the id.

Edit the controller 'edit' method

def edit
	@shirt = Shirt.find(params[:id])
end

Create the view for 'edit'

It makes sense to use the same code for edit that we use for new. The form builder is smart enough to know it is an existing object and not a new object so will prefill the form and also change the button to say 'update'

You can copy the code used above (for errors and the form fields) and move this to a partial and then simply render this partial

This uses the same code to restrict the input parameters as our create method, so it makes sense to move the param restrictions to its own method

def update
	@shirt = Shirt.find(params[:id])
    if @shirt.update(permitted_params)
  		redirect_to '/'
    else
        render 'edit'
    end
end

protected #can't be routed to
def permitted_params
    params.require(:shirt).permit(:name, :description)
end

####Destroy You will need to create a link to root. This will need to submit a DELETE request (not GET). Write your link like…

<%= link_to "delete", shirt_path(shirt), :method => 'delete', :confirm => "are you sure?" %>

Where shirt is the id of the shirt you want to delete. Confirm will generate a javascript confirmation popup which may be blocked by popup blockers.

Then define on your controller the destroy method

def destroy
	@movie = Movie.find(params[:id])
	@movie.destroy

	redirect_to '/'
end

Note: Destroy differs from Delete in that it will run any "before destroy" code first. Delete will only run the SQL statement.

####Update, Create, Destroy always redirect when success! This is because if you do not and the user refreshes the page, it will reattempt the same command.

###REST Conventions

get '/shirts' => 'shirts#index' - homepage  
get '/shirts/x' => 'shirts#show' - view  
get '/shirts/new' => 'shirts#new' - get the form to create  
post '/shirts' => 'shirts#create' - submit form to create  
get '/shirts/x/edit' => 'shirts#edit' - get the form to update  
put '/shirts/x' => 'shirts#update' - submit the form to update  
delete '/shirts/x' => 'shirts#destroy' - delete

####Automatically create routes with 'resources'

you can add all of these simply using 'resources :shirts' in your routes file you can then use "rake routes" in terminal to list what routes are configured since they are not visible in your code. This will also tell you the path to map to these

Note: this should refer to your table name (plural) not your model name!

if you wish to restrict which routes are creatd rather than creating all 7 you can use…

resources :shirts, :only => [:create,:new,etc...]

####Collections and Members

Example resource route with options:

resources :movies do  
	member do  
		get 'search'  		
	end  
	collection do  
		get 'reviews' 
	end  
end

member will create a route…
get 'movies/search' => 'movies#search'

collection will create a route…
get 'movies/:id/reviews'

###Naming Conventions ####Model Naming Convention

Table: orders
Class: Order
File: /app/models/order.rb
Primary Key: id
Foreign Key: customer_id
Link Tables: items_orders

####Controller Naming Convention Note: Plural!
Class: OrdersController
File: /app/controllers/orders_controller.rb
Layout: /app/layouts/orders.html.erb

####View Naming Convention

Helper: /app/helpers/orders_helper.rb
Helper Module: OrdersHelper
Views: /app/views/orders/… (list.html.erb for example)

####Tests Naming Convention

Unit: /test/unit/order_test.rb
Functional: /test/functional/orders_controller_test.rb
Fixtures: /test/fixtures/orders.yml

###Authentication post 'login' => 'sessions#create'

class SessionsController < ApplicationContoller find username in the database with the specified username and password if match found, then authenticated and redirect! end

but to tell the front end that the user is logged in we need to tell it that the user is logged in on every single request. To do this, we send a cookie which is saved by the browser and sent back to the server on every subsequent http request. This will contain the user_name in an encrypted format.

####Devise There is a gem to facilitate this called 'devise'

  • Edit your Gemfile in the root of your app and add…
  • gem 'devise'
  • Save the 'Gemfile'
  • In terminal in the apps root folder run the following...
  • bundle install
    Note: If you're server is currently running it will need to be restarted!
  • rails generate devise:install
  • rails generate devise MODEL (where MODEL = the model you wish to create i.e. user) This will generate a migration to create the model / table, remember no plural!
  • rake db:migrate

This however will not let us modify the devise views to 'create an account', 'reset password' etc… To do this you also need to run from terminal...

rails generate devise:views

Creating a user via console...

x = Admin.new
x.email = "nizmox@gmail.com"
x.password = "something"
x.password_confirmation = "something"
x.save

Note: Password and Password_confirmation are not fields in the database. These are methods that set the encrypted password fields in the database! Thus you cannot see them via the 'Admin' model.

Then in our Model we can add...

before_action :authenticate_user!, :only => [:new, :create]

In :only, we specify which acttions require authentication. Any attempt to access these pages without being logged in redirects to the login page.

To add a login / logout link we can use…

<% if user_signed_in? %>
	<span>
		You are logged in as <%= current_user.email %> - 
	</span>
	<%= link_to 'Logout', destroy_user_session_path, :method => 'delete' %>
<% else %>
	<%= link_to 'Login', new_user_session_path %>
<% end %>

This could be added to our application.html.erb file so it's available on all pages

If you want to give the user feedback on logging in and logging out being successful you can use…

<p>
	<%= flash[:notice] %>
</p>

<p>
	<%= flash[:error] %>
</p>

###Database relationships foreign key - a primary key from another table. e.g. a method to join records together.

These should be named 'modelname_id'

defining relationships…

class Movie < ActiveRecord::Base
	belongs_to :director
end

class Director < ActiveRecord::Base
	has_many :movies
end

using these methods…

director = Director.where(:name => 'David Lynch').first
	
director.movies.each |movie|
	puts movie.name
end

for many to many relationships we need a 'join table' with two foreign keys e.g. if we have movies and actors, we might have a join table called cast member which contains the movie_id and actor_id

class Actor < ActiveRecord::Base
    has_many :cast_members, :through => :cast_members
end
	
class Movies < ActiveRecord::Base
	has_many :movies, :through => :cast_members
end
	
class CastMember < ActiveRecord::Base
	belongs_to :movie
	belongs_to :actor
end

####Implementing relationships imagine we have a 1 to many relationship e.g.
user = one
posts = many

Generate a migration to add the necessary foreign key to posts. Remember this must be named user_id (modelname_id) and a key must be an integer!

Note: See below for a shortcut when generating your model

Open post.rb and add the line…

belongs_to :user

(belongs_to should always be singular!)

Open user.rb and add the line…

has_many :posts

(has_many should always be plural!)

You can now define the foreign key using the relationship

user.post << post (object)

This then allows you to go…

user = User.find(x)
user.posts

This will return an object similar to an array containing all posts for the user.

Or…

post = Post.find(x)
post.user

This will return the user object who created the post.

#####Using relationships with Devise If you have a user -< posts relationship you can use...

current_user.posts.create(params)

This will automatically populate the correct user_id on the post based on who is logged in.

####Implement relationships when creating a Model When generating a new model, you can add the foreign keys using references by editing your migration.

t.references :story
t.references :user

###Bootstrap

save downloaded css files here (bootstrap.css and bootstrap-theme.css)
/vendor/assets/stylesheets

edit
/app/assets/stylesheets/application.css

add the following lines

*= require 'bootstrap'
*= require 'bootstrap-theme'

Note: You need to input this with the *= !!! This should be input in between *= require_self AND *= require_tree.

If this was your own files css/javascript/stylesheets you'd put these in app/assets/subfolder

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment