Rails: Active Record callbacks

There are simple filter-like, provided methods (aspect oriented style), to deal with the life and death cycles of models, the most common are:

  • before_create
  • after_create
  • before_save
  • after_save
  • before_destroy
  • after_destroy

If for any reason, the method returns false, the chain of events will stop, for instance:

def before_create
  false
end

will effectively stop any model creation.

To make things more readable, it is recommended you move your filters to the top of the model, just below the validation section, and list it as:

   after_create :email_article_author
   ...
   def email_article_author
      puts "stuff here..."
   end

This way you can also put multiple methods (comma separated) in the filter.

Rails: model associations

In SQL terms, you associate two tables using a foreign key reference in one, that points to an id on the other.

In rails, the convention is:

#{singular_name_of_parent_class}_id

For example: table articles has comments associated with it, so in the comments table there is an “article_id” column that points the records in there to the ids on the articles table. Once those tables are associated, you can do stuff in rails like this:

article.comments

One to One associations

The example below is establishing an association via the user_id:

rails generate model User email:string password:string

rails generate model Profile user_id:integer name:string birthday:date bio:text color:string twitter:string

To formalize the “marriage”, inside you model you can declare the one to one relationship as follows:

class User < ActiveRecord::Base
has_one :profile
end

class Profile < ActiveRecord::Base belongs_to :user end Once that is setup, you can do stuff like: user.create_profile :name => ‘Jane Doe’, :color => ‘pink’

To create a profile associated with that user

One to many associations

In this example, we are adding the user_id column to an existing “articles” table, via:

rails generate migration add_user_id_to_articles user_id:integer

Once you generate and run your migration, you upgrade your model to be able to handle the one to many relationship:

belongs_to :user # In the article model

has_many :articles # In the user’s model

You could also specify how the records are to be retrieved when calling all articles in a given user:

has_many :articles, -> { order('published_at DESC, title ASC')}

When you are dealing with dependencies, you can also specify what happens if a given record is deleted. For example, if the user’s record is deleted, you can delete all the articles associated with it via internal rails callbacks, by specifying the dependency in the model:

has_many :articles, -> { order(‘published_at DESC, title ASC’)},
:dependent => :destroy

And, that is also the difference between the destroy and the delete methods: the destroy one will call the internal rails callbacks when a record is erased, and follow through erasing the associated records.

Many to many relationships

If two tables have records in both sides associated with many records in the other, this is usually consider a has_and_belongs_to_many relationship. There is usually an extra table in the middle that stores the relationships themselves. If, for example, we have an “articles” and “categories” tables associated this way, rail will usually generate a table in the middle called articles_categories, which will store the ids of both as an association record.

This class of in-between, join table, is special in the sense it doesn’t have a primary key on its own. In this case, the migration file in rails will have something like:

      def change
        create_table :articles_categories, :id=> false do |t|
          t.references :article
          t.references :category
        end
      end

Notice how the :id=>false to avoid creating that.

Important: Not sure if there is a bug in rails, but the migration above produce an articles_categories table, with no columns… I had to manually add them to the schema.db file (definitely a “don’t do it”) to be able to continue. I am still searching for the issue. But be aware of that!

In your category and article models, you will specify the relationship as:

has_and_belongs_to_many :categories
has_and_belongs_to_many :articles

Going further with this, if you have a comment model, which is associated to the articles model, and you want to query all the comments a user has, you have to do that by using the articles model as the join table (or in substitution of it). This is expressed as has_many :through

In this case, you will create some “replies” object, that express the responses (via comments) that a user got in their articles:

has_many :replies, :through => :articles, :source => :comments # at the user's model

So now you have access to queries like:
user.replies.empty?
user.replies.size
user.replies.first
article.comments.create(:name => ‘Guest’, :email => ‘guest@example.com’, :body => ‘Great article!’)

Rails: Cucumber testing

The actual tests go under the “features” folders. You save it as:

features/some_feature.feature

Here’s a sample feature file:

Feature: Creating projects
In order to have projects to assign tickets to
As a user
I want to create them easily

Background:
  Given I am on the homepage
  When I follow "New Project"
Scenario: Creating a project
  And I fill in "Name" with "TextMate 2"
  And I press "Create Project"
  Then I should see "Project has been created."
  And I should be on the project page for "TextMate 2"
  And I should see "TextMate 2 - Projects - Ticketee"

Scenario: Creating a project without a name
  And I press "Create Project"
  Then I should see "Project has not been created."
  And I should see "Name can't be blank"

This is a sample of how you can add steps to expand the test above:

when /the project page for "([^"]*)"/
  project_path(Project.find_by_name!($1))

To run the tests:

$ rake cucumber:ok

If you want to run just one test (you should have run bundle install –binstubs in order to be able to do this:

bin/cucumber features/viewing_projects.feature

Or
rake cucumber FEATURE=features/your_feature.feature

Rails: before_filter

In your controller, you can have as many as you need, they get executed in the order they are set. Note how in the example below we are also storing something from the last post before the controller action runs:

class SearchesController < ApplicationController
before_filter :only => :create do |controller|
session[:last_search] = controller.params[:search][:query]
end

before_filter :do_something_else_method_inside_your_controller_here
before_filter :and_another_one

Also, if you need to read params, you will have to use the block version above, because params can’t be passed to methods, and won’t be readable in them if they run via before_filter

Example of using a block:

  before_filter { |c| c.valid_session_key params }

  def valid_session_key(params)

  end

Ruby on Rails: Making Rake do tasks

First, you generate the task via the command line:

$ rails g task your_task_name_here

Then, open the newly generated task file (lib/tasks/your_task_name_here.rake

And fill out the guts (an example here):

namespace :crawl_quotes do
desc “Crawls for stuff”
task :get_stuff => :environment do
require ‘mechanize’
agent = Mechanize.new

agent.get(“http://www.somewebpagehere.com”)

agent.page.search(“.somecssclass”).search(‘a’).each do |index, item|
# Do something here
end

end

Once that is setup, all you have to do is run the task via the command line (or any other way you have for running scripts):

$rake crawl_quotes:get_stuff      (that is the name of the task namespace, the :, and the specific task you are running

Ruby On Rails: devise basics

Installation

In your gem file:

gem “devise”

$ bundle install
$ rails g devise:install

After that, your config files will be at:

config/initializers/devise.rb

Time to configure the user’s model:

$ rails g devise user
$ rake db:migrate

Note that at this point, you have a set of automatic views that are internal to devise. It is advisable that you run the following command, so you can customize those yourself in your views:

rails generate devise:views

 

Set pages so only login users can see them

In your controller, add the following line:

before_filter :authenticate_user!, :only => [:index, :new, :destroy]

Helpers

current_user contains information about the current user. for example current_user.email

user_signed_in? boolean to test if a user is signed in or not, use it as:

<% if user_signed_in? %>
  <%= link_to 'Sign Out', destroy_user_session_path, method: :delete %>
<% end %>

user_session is the session attached to the user, you can store stuff in it like:
user_session[:somevarname] = “somevarvalue”

Options
In the user’s model, confirmable takes care of sending a confirmation email to newly created accounts

:confirmable

You also need to add a migration along with the confirmable option:

class AddConfirmableToUsers < ActiveRecord::Migration
  def up
    add_column :users, :unconfirmed_email, :string
    add_column :users, :confirmation_token, :string
    add_column :users, :confirmed_at, :string
    add_column :users, :confirmation_sent_at, :datetime

    add_index :users, :confirmation_token, :unique => true

    User.update_all(:confirmed_at => Time.now) #your current data will be treated as if they have confirmed their account
  end

  def down
    remove_column :users, :unconfirmed_email, :confirmation_token, :confirmed_at, :confirmation_sent_at
  end
end

Rails and devise: building associations at record creation time

In your “create” method, on your controller:

@search = current_user.searches.build(params[:search])

Assumes:

  • current_user contains your user record (via devise)
  • search, searches is the model of the record you are trying to create
  • build is just there to marshal your posted form parameters, so no need to include them (RoR is magical like that)
  • current_user.searches is building the relationship between those two, so the search you are creating will have the associated user_id on it

So your User model:

has_many :searches, dependent: :destroy

And your Searches model:

belongs_to :user

This is the very basic association operation. For more details and the list of all possible combinations you can form (from the source):

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

 

 

Ruby On Rails: barebones site template from the ground up.

We are talking about quick demos, site bootstraping kind of deal here. So don’t expect robust, detailed explanations.

Also, assuming you have Ruby / Rails / Git / postgresql / RVM (optional) installed. Barebones, cutting through most of the commands explanations. Aim to push site and ideas fast out there.

$rails new first_app -d postgresql

-d postgresql to create this with postgres

NOTE: postgresql (and role creation) have some issues. It is not a straight process, you can always default to the mysql default in your local server, and harden it on production (if saving time in a demo is an issue).

To secure your app, create a role for the postgres database specific to your app.

In your postgres admin, click on the Login Roles, add Login Role. Make sure the new role can create database objects. Make sure you set a password for it as well.

Then, in config/database.yml you need to plugin the username / password you just created  ( in all the diff databases: development, test and production)

Modify your gem file as follows

$bundle install –without production –binstubs

binstubs makes it easier to run tests later (the command is shorter). It stores binaries so they don’t have to be pre-compiled everytime you run tests.

Speaking of tests, at this point you can generate the skeleton to run tests:

$rails generate cucumber:install
$rails generate rspec:install

$rake db:create

$rails server // just to make sure you have a local server setup

$git init

$git add .

$git commit -m “Initial commit”

[optional] Create a bitbucket (or github) repo:

git remote add origin https://usernamehere@bitbucket.org/usernamehere/projectnamehere
git push -u origin --all # to push up the repo for the first time

Download and install heroku toolbelt (if not there yet)

https://toolbelt.heroku.com/

$heroku login

$heroku create [name of your app here]// inside your app’s directory

// Create the basic model for your users:

$rails generate scaffold User first_name:string last_name:string

// By convention, rails likes models to be singular, and camel cased (starts with a capital letter). As in “User”

// At this point, you should have the basic CRUD operations for users at:

localhost:3000/users

// To create the user’s authentication stuff:

$ rails generate devise:install

// And follow the instructions printed afterwards (no need to replicate them here)

$ rails generate devise User

$ rails generate devise Admin

// Important! protect your user’s section by adding the following to app/controllers/users_controller.rb:

before_filter :authenticate_admin!

// And also, make sure users can’t register as admins (inside the admin’s model, app/models/admin.rb)

devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable

// Notice that :registerable has been removed. If you need to create admins, you will have to do it via direct db access, or by using the console and bypassing momentarily that restriction, or, you can bring it back and ask for an extra token at admin registration time, but then you will have to manage / distribute those tokens

$bundle exec rake db:migrate // Update the DB with it

$ rails generate controller StaticPages home help about contact_us–no-test-framework // Create home and help pages, which can contain dynamic variables. It generates all your views and controllers

– Modify your public/index.html with something other than the default Rails page (or remove it if you are sending users to another page via the router)

root to: ‘static_pages#home’ // redirects “/” to static pages controller, home method

match ‘/help’, to: ‘static_pages#help’ // redirects to help page

– Put any other pure static pages in this directory as well (public/hello.html, etc)

Example of how to integrate the login and signup links in a view after devise is in place:

<% unless admin_signed_in? -%>
  <p><%= link_to ‘Sign up as Admin’, new_admin_registration_path %></p>
  <p><%= link_to ‘Sign in as Admin’, new_admin_session_path %></p>
<% end -%>
<% unless user_signed_in? -%>
  <p><%= link_to ‘Sign up as User’, new_user_registration_path %></p>
  <p><%= link_to ‘Sign in as User’, new_user_session_path %></p>
<% end -%>
<% if user_signed_in? -%>
  <p><%= link_to ‘Token Authentication Example’, token_path(:auth_token => current_user.authentication_token) %></p>
<% end -%>

Example of how to protect your pages from access (via the controller):

before_filter :authenticate_user!

or

before_filter :authenticate_admin!

$rails generate scaffold Micropost content:string user_id:integer // Create an object that will be associated with a user

// Setup your basic models associations at this point:

class User < ActiveRecord::Base // Inside apps/model/user.rb
attr_accessible :email, :name
has_many :microposts
end

class Micropost < ActiveRecord::Base // Inside apps/model/micropost.rb
attr_accessible :content, :user_id

belongs_to :user

validates :content, :length => { :maximum => 140 }
end

// Next, save the changes, and push them locally and in Heroku:

$ git add .

$ git commit -m “Add models”

$ git push heroku master

$ heroku run rake db:migrate

$ heroku open // Make sure things are handy dandy

//  A bit of automatic styling

app/assets/stylesheets/custom.css.scss // Add this file, with the following line:

@import “bootstrap”; // Now you can use twitter’s bootstrap to style your app!