Rapid Prototyping with Rails: Lesson 2, Walk-through (model-backed forms)

Rails Model Backed Forms - Create a Resource called: Dog

Command to change port: rails s -p 3001

  1. Run migration
rails g create_dogs

    class CreateDogs < ActiveRecord::Base
      def change
        create_table :dogs do |t|
          t.string :name
          t.integer :height_in_inches, :weight_in_lbs
        end
      end
    end
  1. Create dog.rb under Models
class Dogs < ActiveRecord::Base
end
  1. rake db:migrate or bundle exec rake:db migrate

  2. Hop into Rails Console to add instances of Dog class

  3. Create Resources or Routes manually

1. Create Using Resources
    resources :dogs, except [:destroy]
2. Write routes manually in routes file
    get '/dogs/', to: 'dogs#index'
    get '/dogs/:id', to: 'dogs#show'
    get 'dogs/new', to: 'dogs#new'
    post '/dogs',   to: 'dogs#create'
    get '/dogs/:id/edit', to: dogs#edit
    patch '/dogs/:id', to: dogs#update
    delete  'dogs/:id', to: dogs#destroy
  1. Create Dogs Controller
#dogs_controller.rb
class DogsController < ApplicationController
  def index
  end
end
  1. We will get an error because there no index.html.erb under the dogs folders within the views folder

  2. Create instance variable to pass into the template

def index
  @dogs = Dogs.all
 end
  1. Better yet, in your model, you could:
class Dog < ActiveRecord::Base
    def info
      "#{self.name} is #{self.height_in_inches} inches tall and weighs #{self.weight_in_lbs} lbs."
    end
  end

Something like the above is okay, but not okay to add html to that statement. In no way should you ever add presentational concerns at the model level. Application_helpers is for formatting logic on the user interface

  1. A better solution is to add this code to the application_helper.
#application_helper.rb
    def dog_info(dog)
      "#{dog.name} is #{dog.height_in_inches} inches tall and weighs #{dog.weight_in_lbs} lbs."
    end
#index.html.erb
    <h2>All the dogs </h2>

    <ul>
    <% @dogs.each do |dog| %>
      <li><%= dog_info(dog) %></li>
    <% end %>
    </ul>

Lets add the new action to the controller:

  1. Add link to navigation bar for a new dog:
<div class='nav_item'>
    <%= link_to 'New Dog', new_dog_path, class: 'btn btn-success btn-small' %>
  </div>
  1. Must update routes to include the named route:
get '/dogs/', to: 'dogs#index'
    get '/dogs/:id', to: 'dogs#show'
    get 'dogs/new', to: 'dogs#new', as: 'new_dog'
    post '/dogs',   to: 'dogs#create'
    get '/dogs/:id/edit', to: dogs#edit
    patch '/dogs/:id', to: dogs#update
    delete  'dogs/:id', to: dogs#destroy
  1. You must list routes in proper order - order matters
get 'dogs/new', to: 'dogs#new', as: 'new_dog'
    get '/dogs/', to: 'dogs#index' as: 'dogs'
    get '/dogs/:id', to: 'dogs#show'  as: 'dog'
    post '/dogs',   to: 'dogs#create'
    get '/dogs/:id/edit', to: dogs#edit
    patch '/dogs/:id', to: dogs#update
    delete  'dogs/:id', to: dogs#destroy
  1. Create
#new.html.erb
    <h4> Create a new dog </h4>

    <%= form_for @dog do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>
      <br/>
      <%= f.label :weight %>
      <%= f.text_field :weight_in_inches %>
      <br/>
      <%= f.label :height %>
      <%= f.text_field :height_in_inches %>
      </br>
      <%= f.submit "Create dog" %>

    <% end %>

Create

def create

  @dog = Dog.new(params.require(:dog).permit!)

    if @dog.save
      flash[:notice] = "You created a dog!"
      redirect_to dogs_path
    else
      render :new
    end
  end
end

Render can take a string or a method. Form_for looks at @post and determines are you a new post or are you an existing post. If it is in a existing object, then the _method is injected to execute a patch, rather than post. Params is a nested hash.

Edit

#dogs_controller.rb
def edit
  @dog = Dog.find(params[:id])
end
edit.hmtl.erb

    <h4> Create a new dog </h4>

    <%= form_for @dog do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>
      <br/>
      <%= f.label :weight %>
      <%= f.text_field :weight_in_inches %>
      <br/>
      <%= f.label :height %>
      <%= f.text_field :height_in_inches %>
      </br>
      <%= f.submit "Create dog" %>
    <% end %>

Update

def update
@dog = Dog.find(params[:id])

    if @dog.update?
      flash[:notice] = Your dog has been updated!
      redirect_to dogs_path
    else
      render :edit
    end
  end
end

Resources

So all the routes from below:

get 'dogs/new', to: 'dogs#new', as: 'new_dog'
    get '/dogs/', to: 'dogs#index' as: 'dogs'
    get '/dogs/:id', to: 'dogs#show'  as: 'dog'
    post '/dogs',   to: 'dogs#create'
    get '/dogs/:id/edit', to: dogs#edit
    patch '/dogs/:id', to: dogs#update
    delete  'dogs/:id', to: dogs#destroy

Can be replaced with resources :dogs

Add an edit link to the index page:

index.html.erb
    <h2>All the dogs </h2>

    <ul>
    <% @dogs.each do |dog| %>
      <li><%= dog_info(dog) %></li><%= link_to '[edit]', edit_dog_path(dog)%></li>
    <% end %>
    </ul>

Extract common code from new and edit forms into partial:

_form.html.erb
    <%= form_for @dog do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>
      <br/>
      <%= f.label :weight %>
      <%= f.text_field :weight_in_inches %>
      <br/>
      <%= f.label :height %>
      <%= f.text_field :height_in_inches %>
      </br>
      <%= f.submit "#{@dog.new_record? ? 'Create' : 'Update'}dog" %>
new.html.erb
<h4> Create a Dog </h4>
<%= render 'form' %>
edit.hmtl.erb
<h4> Edit a Dog </h4>
<%= render 'form' %>

Validations

dog.rb
validates :name presence: true
validates :height_in_inches presence: true
validates :weight_in_lbs presence: true

rails console

Failure to satisfy strong_paramters will generate errors. There are several methods we can call regarding errors in our rails console

dog = Dog.new(), then dog.save will generate the errors array on the object itself

  1. dog.errors #will list errors
  2. dog.valid? # will be false because there were errors
  3. dog.errors.empty? #false
  4. dog.errors.full_messages #will display an array of current error messages that you can use to display on the user interface

Display errors in _form.html.erb

_form.html.erb
  <% if @dog.errors.any? %>
  Please correct the following errors:

  <ul>
    <% @dog.errors.full_messages.each do |msg|
    <li><%= msg %></li>
    <% end %>
    </ul>
  <% end %>

<%= form_for @dog do |f| %>
  <%= f.label :name %>
  <%= f.text_field :name %>
  <br/>
  <%= f.label :weight %>
  <%= f.text_field :weight_in_inches %>
  <br/>
  <%= f.label :height %>
  <%= f.text_field :height_in_inches %>
  </br>
 <%= f.submit "#{@dog.new_record? ? 'Create' : 'Update'}dog" %>

Field with errors

When an 'form_for' is submitted with errors, automatically insert a div called 'field_with_errors' around those fields with errors to allow for styling. This will also cause the form fields to appear wider apart (padding)

Rapid Prototyping with Rails: Lesson 2, Migrations

Items Covered:

  1. Changes to Schema
  2. data definition statements vs query statements
  3. setting up db and creating migration
  4. rolling back
    • up/down methods
  5. How does Rails keep track of migrations we have run
  6. When to modify a migration
  7. What happens if I can't run migrations cleanly from end to end

Data Definitions

Data Definitions are to databases what metaprogramming is to code. * statement that alter the database rather than query statements that manipulate the data within a database * the steup commands before we can even query the database * statements that alter the metadata and schema * these occur outside of the code (kinda like setting up the spreadsheet before entering data) * migrations are a good example of this

Migrations

We never want to push our database to other developers when working on a team, we want to push our schema however, and this is done in the form of migrations. - by default, all sqlite3 files under db will be ignored when pushing to git, as per gitignore - To list rake commands pertaining to db:

rake -T | grep db

Database Schema Migrations

  • There is table called schema_migrations which stores the migrations of all currently within your database
  • rake db:rollback will roll back you most recently ran

Rollbacks

Have you pushed the migration to github... have you shared locally? If not, then rollback is fine, if not.. don't. Because rollback will not create a diff file name. The code changed and its fine on your end.. but for your team members, since the filename hasn't changed, it will cause their application to blow-up since their migration did not re-run. Instead, create a new migration such as rails g migration rename_users. Always google, "rails migrations methods"

What happens if applications can't run cleanly from end-to-end? One of the first thing your team-members will do is run rake db:migrate when cloning your projectand migrations that don't run smoothly is a red flag. Schema.rb - can also be loaded when your migrations don't run from to end-to-end.

=> rake db:schema:load
  • you can search for available commands using
=> rake -T | grep schema

Rapid Prototyping with Rails: Lesson 2, part 3

Items covered:

  1. Nested Resources
  2. Helpers
  3. Fat Models, Thin Controllers
  4. Drop-downs
  5. Application Helpers

Review nested resource Setting up categories association in post form

  1. How to create a comment in the show posts page.

1.1 We need to know how the route to submit to: * rake routes | grep comments (console)

or

  • localhost:3000/rails/info/routes (browser)

1.2 How do we tell our model backed form to submit to the POST create comments action?

By adding @comments to our model backed form_for POST create posts:

show.html.erb
  <h5>Create a Comment </h5>
  <%= form_for [@post, @comment] do |f| #[parent obj, child obj] to create this 'posts/id/comments'
    <f.text_area :body %>
      <br/>
    <%= f.submit "Create Comment", class: 'btn btn-primary' %>
  <% end %>

If ever you feel as though you can't tell which object you are passing through the form_for: look at the create controller action - there it will tell you where you one which object you are calling .save on. This is the object that will generate the error if it does not pass validations.

Helpers: What if we wanted to link_to post.url in the show page?

show.html.erb

<h4> Hi there, you're viewing <%= link_to @post.title, @post.url.starts_with?('http://') ?
@post.url : "http://#{post.url}"" %></h4>

Helpers:

We don't want our model to be concerned with presentation level concerns.

application_helper.rb
    module ApplicationHelper
      def fix_url
        str.starts_with?('http://') ? str : "http://#{str}"
      end
    end
show.html.erb
  <h4>Hi there, you're viewing <%= link_to post.title, fix_url(post.url) %></h4>

Fat Models, Thin Controllers

Move logic to a model method Problems with logic at the controller level: 1. Sometimes you can't tell where the input stream is (console, another action) 2. Its really hard to test

**How to associate a post and category in a form? In order to use the form_for, you must use a getter or setter (colmun name of virtual attribute

In console:

1. post = Post.first
  2. post.categories.first.name => "Rubyists"
  3. post.categories = [] => Delete category associations
  4. post.category_ids = [2, 3] => add category_ids 2 and 3 to post, saves to database
  5. post.categories.size => 2
  6. post.category_ids = [4] # removes categories previously entered and saves caegory_id 4

How to create a drop down

Learn how to do use Select forms in HTML first:

*Selecting one option:
<select>
  <option value='1'>option 1</option>
  <option value='2'>option 2</option>
</select>
** Selecting multiple options
<select name='post[category_ids][]' multiple ='multiple'>
  <option value='1'>Rubyists</option>
  <option value='2'>Pythonistas</option>
</select>

But helpers will save in memory your previous selections rather than hard coding when edit/updating a post.

Note: that name='post[category_ids]' requires an [] for arrays.

Strong_params for checkboxes or drop-downs

-> category_ids requires an array
    params.require(:post).permit(:title, :url, :description, category_ids:[])

Application_helper

Is were you store redundant, reusable code that shouldn't be stored at the model layer. The model should never deal presentation level concerns but rather pure logic, data structures (arrays and hashes). It should present data in whatever form and the views and helpers should just deal with it accordingly. Helpers are good for extracting logic that deals with the view but doesn't belong to a particular model or resource.

Rapid Prototyping with Rails: Lesson 2, part 2

Topics Covered:

  1. Validation
  2. Errors
  3. General Create Pattern
  4. Update Path
  5. Form Partial
  6. Before_action
  7. Nested Resources

Validations

are added always at the model layer:

class Post < ActiveRecord::Base
  has_many :comments
  validates :title, :presence true
end

Now if you go into the rails console and do Post.new without a title, an error will not occur until you try to hit the database, so either Post.save or Post.create.

When you try to do so, the console will return "False". This is because, there is an if clause in the create method. See below. Running post.errors in your console will print out an array called ActiveModel Errors and will tell you all fields that require validation... in other words.. can't be blank.

def create
  @post = Post.new(post_params)

  if @post.save
    flash[:notice] = "Your post was created."
    redirect_to posts_path
  else
    render 'new'
  end
end

Errors

post.errors will generate an array. If you want to parse out the messages... type post.errors.full_messages

If post.save/post.create is unsuccessful, then you will 'render' the new form. You will 'render' and not 'redirect' becasue we need to access the errors on the instance variable in order to display the errors. When the validations arent met and it fails to save, save, false is returned in the console and the errors array is populated. However, as it stands, the new form will be rendered.

How to display the Errors (Rails Backed Forms Only)

<h3> Creating a Post </h3>
<% if @post.errors.any? %>

  <h5> There were some errors: </h5>

  <ul>
    <% @post.errors.full_messages.each do |msg|
    <div class='alert alert-error'>
        <li><% msg %> </li>
      <% end %>
    </div>
  </ul>
<% end %>
<h4> Rails model-backed form helpers </h4>
  <%= form_for @post do |f| %> # @post could also be Post.new
  <%= f.label :title %>
  <%= f.text_field :title %>

  <%= f.label :url %>
  <% f.text_field :title %>
  <br/>
  <%= f.submit "Create Post", class: 'btn btn-primary' %>
<% end %>

Field with Errors

Rails creates a div class to allow you to easily point out or style your fields with errors called "div class = fields with errors"

Form_for does alot of guessing It guesses that New forms should go to /'posts'

If you:

<%= form_for @post do |f| %> # @post could also be Post.new

If you:

<%= form_for Post.first do |f| %> # Will bring up the first Post object

When doing so, that Post.first is calling method='POST' with a route of '/posts/id' - which is not a listed route, therefore should not be valid.

But instead of erroring out, it is going to the post/update controller action.

Why?

This is bc the in addition to the Utf8 and the authenticity token, there is hidden method called _method = 'patch'

_method is what Rails uses to determine which HTTP verb is used in the routes file or input method. This is because not every browser supports all HTTP methods, just most likely GET and POST.

  1. If the object is an existing object, Rails will automatically give it an _method = "PATCH" and the route will become '/post/id'

  2. The new post form and the edit post form can be identical because method in formfor will take care of it.

  3. Now you can create a partial to extract the form code that will be used for both new and update partials

So now: 1. Create a partial called _form.html.erb under posts 2. Extract common code

_form.html.erb
        <% if @post.errors.any? %>
          <h5> There were some errors: </h5>
          <ul>
            <% @post.errors.full_messages.each do |msg|
            <div class='alert alert-error'>
                <li><% msg %> </li>
              <% end %>
            </div>
          </ul>
        <% end %>
<h4> Rails model-backed form helpers </h4>
      <%= form_for @post do |f| %> # @post could also be Post.new
      <%= f.label :title %>
      <%= f.text_field :title %>

      <%= f.label :url %>
      <%= f.text_field :title %>
      <br/>
      <%= f.submit(@post.new_record? ? "Create Post" : "Update Post", class:
      'btn btn-primary' %>
    <% end %>
  1. Then update new_html.erb
<h3> Creating a Post </h3>
  <%= render 'form' %>
  1. Then do the same for edit.html.erb
<h3> Edit a Post </h3>
  <%= render 'form' %>
  BUT you must also ensure that you have all your necessary instance variables
  created so in your controller
def edit
        @post = Post.find(params[:id])
      end
  which will route to 'post/:id/edit'... named route edit_post_path(post)

UPDATE

def update
    @post = Post.find(params[:id])

    if @post.update(post_params)
      flash[:notice] = "The post has been updated"
      redirect_to posts_path
    else
      render :edit
    end
  end

Remember since your _form.html.erb is being used by four actions: new, create, edit, and update, So you have to make sure you have @post defined in each action in your controller or an exception will be thrown. Consequences of dry.

Before_action

For Rails 4, its beforeaction For Rails 3, its beforefilter

Since we are using @post = Post.find(params[:id]) several times in our controller by various actions (show, edit, and update), we should create a method called setpost and pass it the beforeaction, then we can remove @post from those

class PostController < ApplicationController
before_action :set_post, only: [:show, :edit, :update]

  def show
  end

  def edit
  end

  def update
  end

  def set_post
    @post = Post.find(params[:id])
  end
end

We use before_actions for two reasons 1. Set up an instance variable 2. Set up a redirect based on some condition (authentication)

Nested Resources

Nested Routes

  • Redirect => URL
  • Render => template

How to create a comment on the Show Post page:

Routes File:

resources :posts, except: [:destroy] do
  resources :comments, only: [:create]
end
show.html.erb (show posts page)
<h5> Create a Comment </h5>

  <%= form_for [@post, @ comment] %>
    <%= f.text_area :body %>
    <br/>
      <%= f.submit "Create Comment" class: "btn btn-primary" %>
  <% end %>

'/posts/3/comments' * We are trying to @post must be an existing post and @comment has to be an existing comment.

PostsController.rb
def show
  @comment = Comment.new
end

Create a CommentsController

comments_controller.rb

class CommentsController < ApplicationController

def create
  @post = Post.new(params[:post_id]) #Always run params in binding.pry (in your controller) to check the attribute name. Nested routes always have the parent route appended to the id, therefore it would be 'post_id'

  @comment = Comment.new(params.require(:comment).permit(:body))

  #we could also associate comment and post
      @comment.post = @post.comments
      or
      you could change the above line for @ comments to create an in memory post.comment
      object with that has the post_id already filled out.
        @comment = @post.comments.build(params.require(:comment).permit(:body))
  if @comment.save
    flash[:notice] = "Your comment was added"
    redirect_to post_path(@post)
  else
    render 'posts/show' #must be a template file... you are trying to indicate
                        where the file is located
  end
end

#typially, we would just create a seperate method for comment_params, we aren't doing
that because we aren't trying to reuse this line of code for the Update action, only create.
end

CRUD

CRUD is an acronym that stands for "create", "retrieve", "update" and "delete". You'll hear many developers say something like "we just need a CRUD web interface for tickets". This is what Rails gives us out of the box: an ability to perform basic CRUD actions on a resource as a web application. Below is how the default Rails actions map to CRUD:

  • Create: new, create
  • Retrieve: index, show
  • Update: edit, update
  • Delete: destroy

Render vs Redirect

You should also understand what a render is vs a redirect. Render compiles the template into HTML and sends the HTML back as part of the response. Redirect sends back a URL as part of the response; there's no HTML in a redirect. Most browsers follow the redirected URL automatically, and a new request is issued. All redirects will eventually lead to rendering of some template, otherwise your browser will display a "too many redirects" error.

Why does the URL stay at /posts when there's a validation error? Shouldn't it be /posts/new?

The request URL is what's showing up in the address bar, not the response. The response is processed by your browser. The request URL is shown in the address bar.

In the case of a new post form submission, the request URL is /posts. This has nothing to do with the response sent back. The URL only changes on successful post creation because in that case the response is a redirect, and your browser issues a new request, which changes the address bar.

Rapid Prototyping with Rails: Lesson 2, part 1

Items covered: 1. Rails Forms 2. Strong Parameters

Rails Forms

There are 3 ways to create a form 1. Pure HTML 2. Rails Forms Helpers 3. Rails model-backed Forms Helpers

  1. PURE HTML
<h5> Pure HTML Form </h5>
  <form action='/posts' method='POST'>
    Title <input type='text' name='my_title'>

    <br/>

    <input type='submit' class='btn btn-primary'>
  </form>

Rails has something called CSFP - cross site forgery protection built into it. It tries to prevent fake forms from submitting to your website.. without token. This can be disabled in your application_controller.rb

In the above section, where it says

Title <input type='text name='my_title'>

Giving the title attribute a name called "my_title" gives you the ability refer to it as a hash. So params typed into pry would give you:

{"my_title" => "some title"}

or

params[:my_title] would give you "some title" returned

*Hash with indifferent access - means although the key is a string, I can refer to it as a :symbol" So therefore, in your console, you could type:

Post.create(title: params[:my_title]) **This is called mass assignment**
  1. RAILS FORMS HELPERS When creating pure html forms, use form helpers.
<h4> Rails Forms Helpers </h4>
<%= form_tag '/posts' do %>
  <%= label_tag :title, "Input a title" %>
  <%= text_field_tag :title %>
  <br/>
  <%= submit_tag "Create Post", class: 'btn btn-primary' %>
<% end %>

Form helpers, do several things:

  1. Automatically determines the method='POST' B/c it is a form
  2. Security Provides an UTF 8 & authenticity token in a hidden div that helps allows us to submit under cross site forgery protection
  3. Using the label_tag (helper) Gives more guidance to the blind. Label elements correalate with input elements and is read by screenreaders for the hearing impaired.

    1. RAILS MODEL-BACKED FORMS
<h4> Rails model-backed form helpers </h4>
  <%= form_for @post do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title %>

  <%= f.label :url %>
  <% f.text_field :title %>
  <br/>
  <%= f.submit "Create Post", class: 'btn btn-primary' %>
<% end %>

You can mix and match forms and tags (from 2 and 3), if needed.

What is happening here:

The form_for is taking an object (@post). We must define @post in our controller:

def new
      @post = Post.new
    end

This gives us a few liberties:

Post.create(params[:post]) or Post.new(params[:post]) is effectively doing mass assignment,
using the hash assigned to (params[:post])

Associations

has_many :comments
has_many :posts

These become getters and setter bc they are virtual attributes as opposed to the actual column names which are actual attributes

When you have a model-backed form, you must use fields that are virtual or actual attributes because your input will attempt roll up and mass assign. This will not work if the actual attribute does not exist.

f.label :whatever (yes)
  f.text_field :whatever (no)

However, the above can be accomplished with Rails Forms Helpers (#2) or Pure HTML, you can do what you want and just parse it out on the back-end.

URL
      name = (post[:url]) is how rails magic, mass assignment occurs
      In console or during binding.pry, typing params will list
      UTf8
      Authenticity Token
      Post attributes in hash form
      Commit
      Action
      Controller

Typing params[:post] is the same as post.

Strong Parameters
def create
  post = Post.new(params[:post])
end

Will through an error in Rails 4 bc you don't have strong_parameters. They realized that the concern regarding strong parameters and security really didn't occur at the model layer but rather in the action based on user access, roles, etc.

So no-longer a white list of attributes available for massassignment, attraccessible :name :sex :location

So the Rails 4 version is:

def create
  @post = Post.new(post_params)
end

private
def post_params #because we want to reuse this, so lets create a post_params method
  params.require(:post).permit(:title, :url)
end

OR you can assign strong parameters based roles

def post_params
  if user.admin?
    params.require(:post).permit! #permit everything

  else
    params.require(:post).permit(:title, :url) #only permit title and url
  end
end

If in the future, you add other input fields but do not update your strong params, it will just wipe away your entry attempt, it will not throw an error as to let hackers know what ways to pass the security restriction

So Rails 3 is attraccessible :title (whitelist) and Rails 4 is strongparams in controller action.

Now that you have understood post params, next memorize the following:

def create

  @post = Post.new(post_params)

  if @post.save
    flash[:notice] = "Your post was created."
    redirect_to posts_path
  else
    render 'new'
  end
end