23 May 2013

referring to Slide Presentation (Slide Numbers) see PDF - useful-rails-concepts-slides.pdf

Rails API - Slide 3

Rails API

https://github.com/rails-api/rails-api

For new apps - Install the gem if you haven’t already:

gem install rails-api

Then generate a new Rails::API app:

rails-api new my_api

AMS - Active Model Serializers

https://github.com/rails-api/active_model_serializers

The easiest way to create a new serializer is to generate a new resource, which will generate a serializer at the same time:

rails g resource post title:string body:string

This will generate a serializer in app/serializers/post_serializer.rb for your new model. You can also generate a serializer for an existing model with the serializer generator:

rails g serializer post

Example of created serializer:

class PostSerializer < ActiveModel::Serializer
  attributes :id, :title, :string
end

Navigating to the URL localhost/posts would then render a JSON string response. The rails g resource command would already have created an entry in routes.rb to include the posts resource, as such allowing index access, rendering posts.json.

API access, Tokens

to wrap an API, include in routes.rb, for example using the products resource:

namespace :api do
  namespace :v1 do
    resources :products
  end
end

then create a controller in api/v1/products_controller, where the before_filter :restrict_access calls the token authentication, which is an API table containing a row with the SHA1 hash. If you navigate to e.g. localhost/api/v1/products.json?access_token=1234d607b688b43841f1c6ccfe57a69e

module Api
  module V1
    class ProductsController < ApplicationController
      before_filter :restrict_access
      skip_before_filter :require_login, :only => [:index, :show]
      respond_to :json
      caches_action :index, :show
      
      def index
        
        @products = Product.all

        respond_to do |format|
          
          format.json { render json: @products, :only => [:id, :level, :condo_no, :sales_no, :sqr_ft, :schedule_b, :seating, :restaurant, :hair, :food, :herbs_dsf, :pets, :extended_hours, :standard_hours, :category_txt, :availability, :price, :notes] }
        end
        ActiveRecord::Base.include_root_in_json = false
      end
      
      def show
        @product = Product.find(params[:id])

        respond_to do |format|

          format.json { render json: @product, :only => [:id, :level, :condo_no, :sales_no, :sqr_ft, :schedule_b, :seating, :restaurant, :hair, :food, :herbs_dsf, :pets, :extended_hours, :standard_hours, :category_txt, :availability, :price, :notes] }
        end

        ActiveRecord::Base.include_root_in_json = false
      end

      def restrict_access
        api_key = ApiKey.find_by_access_token(params[:access_token])
        head :unauthorized unless api_key
      end

      def restrict_access2
        authenticate_or_request_with_http_token do |token, options|
          ApiKey.exists?(access_token: token)
        end
      end
      
      
    end
  end
end

For token based access - Railscasts episode #352 is helpful http://railscasts.com/episodes/352-securing-an-api?view=asciicast

Security, Strong Params, whitelisting

By default Rails now creates whitelisted accessors in models to control access to attributes. Previously, a security hole was discovered that allowed a malicious web app to send updates to attributes (Mass Assignment) through the helper methods update_attributes and new.

Example of previously unsafe update_attributes method in controller method update:

def update
  @order = Order.find(params[:id])

  respond_to do |format|
    if @order.update_attributes(params[:order])
      format.html { redirect_to @order, notice: 'Order was successfully updated.' }
      format.json { head :no_content }
    else
      format.html { render action: "edit" }
      format.json { render json: @order.errors, status: :unprocessable_entity }
    end
  end
end

See also Rails Guides Security Article on Mass Assignment - http://guides.rubyonrails.org/security.html#mass-assignment

Without any precautions Model.new(params[:model]) would allow attackers to set any database 
column’s value.

The mass-assignment feature may become a problem, as it allows an attacker to set any model’s attributes by manipulating the hash passed to a model’s new() method:

def signup
  params[:user] # => {:name => “ow3ned”, :admin => true}
  @user = User.new(params[:user])
end

Mass-assignment allows passing in a hash to the new method, or assign_attributes= to set the model’s attributes to the values in the hash. It is often used with the parameters (params) hash available in controller, which may be used with malicious intent. Changing URL example:

Simple GET request http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1

It’s worth reading the Rails security Guide - http://guides.rubyonrails.org/security.html

Example of whitelisting params in model - is generated automatically upon resource creation (e.g. rails g resource ProductDetail ProductID:string ProductName:string :ProductUPCCode:string) or scaffold creation (replace resource in generate command with scaffold if one needs HTML views):

class ProductDetail < ActiveRecord::Base
  attr_accessible :ProductId, :ProductName, :ProductUPCCode
end

Routes - Slide 12

Routes

RESTful routes creation with resources :products vs. using match (catch-all for get and post), get, post.

Catch params (params[:id]) with match "store/categories/(:id)" => "store#categories".

Route nesting

Nest routes with e.g.

resources :blogs do
  resources :posts
end

resources :posts do
  resources :comments
end

There is an article by Jamis Buck (37 Signals) about avoiding deeply nested routes - see http://weblog.jamisbuck.org/2007/2/5/nesting-resources. See also Rails Guides routing - http://guides.rubyonrails.org/routing.html

Specifically http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions e.g. resources :photos command creates 7 different routes on the Photos controller:

HTTP Verb Path action used for
GET /photos index display a list of all photos
GET /photos/new new return an HTML form for creating a new photo
POST /photos create create a new photo
GET /photos/:id show display a specific photo
GET /photos/:id/edit edit return an HTML form for editing a photo
PUT /photos/:id update update a specific photo
DELETE /photos/:id destroy delete a specific photo

rake routes

rake routes command shows all available routes and their named paths, which can be used to e.g. generate link helpers. edit_post_comment refers and generates the route GET /posts/:post_id/comments/:id/edit(.:format):

          post_comments GET    /posts/:post_id/comments(.:format)          {:action=>"index", :controller=>"comments"}
                        POST   /posts/:post_id/comments(.:format)          {:action=>"create", :controller=>"comments"}
       new_post_comment GET    /posts/:post_id/comments/new(.:format)      {:action=>"new", :controller=>"comments"}
      edit_post_comment GET    /posts/:post_id/comments/:id/edit(.:format) {:action=>"edit", :controller=>"comments"}
           post_comment GET    /posts/:post_id/comments/:id(.:format)      {:action=>"show", :controller=>"comments"}
                        PUT    /posts/:post_id/comments/:id(.:format)      {:action=>"update", :controller=>"comments"}
                        DELETE /posts/:post_id/comments/:id(.:format)      {:action=>"destroy", :controller=>"comments"}
                  posts GET    /posts(.:format)                            {:action=>"index", :controller=>"posts"}
                        POST   /posts(.:format)                            {:action=>"create", :controller=>"posts"}
               new_post GET    /posts/new(.:format)                        {:action=>"new", :controller=>"posts"}
              edit_post GET    /posts/:id/edit(.:format)                   {:action=>"edit", :controller=>"posts"}
                   post GET    /posts/:id(.:format)                        {:action=>"show", :controller=>"posts"}
                        PUT    /posts/:id(.:format)                        {:action=>"update", :controller=>"posts"}
                        DELETE /posts/:id(.:format)                        {:action=>"destroy", :controller=>"posts"}
               comments GET    /comments(.:format)                         {:action=>"index", :controller=>"comments"}
                        POST   /comments(.:format)                         {:action=>"create", :controller=>"comments"}
            new_comment GET    /comments/new(.:format)                     {:action=>"new", :controller=>"comments"}
           edit_comment GET    /comments/:id/edit(.:format)                {:action=>"edit", :controller=>"comments"}
                comment GET    /comments/:id(.:format)                     {:action=>"show", :controller=>"comments"}
                        PUT    /comments/:id(.:format)                     {:action=>"update", :controller=>"comments"}
                        DELETE /comments/:id(.:format)                     {:action=>"destroy", :controller=>"comments"}

SPA Single Page Apps - Slide 4

Example of Standard jQuery/ Ajax use in Rails

Good jQuery reference for Ajax etc. - http://jqfundamentals.com/

Ajax GET Request

e.g. a music track selector widget:

selector widget

Html page includes two selectors and a link (action), where jQuery references the two id fields categories and sort in the event of click, binding events to these id’s with jQuery’s live capture (one could just use the click event, but then it would not register changes from both selectors):

<script type="text/javascript">

    jQuery(function($) {

        // prod_select ajax get function
        $('#get_category').live('click',function () {
            $.get("/home/articles", {cat: $('#categories').val(), sort: $('#sort').val()}, null, 'script');
            return false;
        });
    });

</script>

Then in the home_controller articles method we refer to an articles.js articles.js.erb file, which has the same name as the articles method (we could call it something else, but then would have to specify name in the respond_to block):

def articles

  category_selected = params[:cat]
  
  sort_by = params[:sort]

  #do some magic with the params...

  respond_to do |format|
    format.html
    format.js
  end
end

articles.js.erb file, inserting a count (through erb) and a partial into the page, updating the displayed list of articles (songs):

/* comment */
$("#articles_count").html("<%= pluralize(@articles.count, 'Article') %>");

/* comment */
$("#articles").html("<%= escape_javascript(render('home/article')) %>");

The render('home/article') refers to a partial _article.html.erb that Rails inserts into the page:

<table>
  <% @articles.each do |article| -%>
     <tr>
     <td><a href = "/articles/<%= article.id %>"><img src="/assets/note.png"/></a></td>
     <td><h2><a href="/articles/<%= article.id %>"><strong><%= article.name %> - <%= h truncate(article.description_en, :length => 80) %></strong></a></h2>

      <p class="summary"><%= article.description_en %></p>

    </td>
    </tr>
  <% end -%>
</table>

Ajax POST Request

Same workflow as GET Request (see above) but jQuery Ajax helper would be using $.post instead of $.get.

Another way to create Ajax POST requests automatically is to use the :remote => true helper in Rails forms:

<% form_tag products_path, :remote => true do %>
  <p>
    <%= text_field_tag :search, params[:search] %>
    <%= submit_tag "Search", :name => nil %>
  </p>
<% end %>

Rails Guides simple Ajax example http://edgeguides.rubyonrails.org/working_with_javascript_in_rails.html#a-simple-example

There are also various Railscasts.com articles on Ajax/ jQuery that might be a useful - see http://railscasts.com/episodes/240-search-sort-paginate-with-ajax?view=asciicast

Ember.js

Refer to Ember.js Guides - see intro video http://emberjs.com/guides/, Github project with code https://github.com/tildeio/bloggr-client

Also Peepcode Episode on Ember - https://peepcode.com/products/emberjs, an inofficial Github project with code (might work, it’s a copy of Peepcode’s commercial offering) https://github.com/jordelver/peepcode-ember-js

Devmynd article on Ember.js - http://www.devmynd.com/blog/2013-3-rails-ember-js

Also - http://www.embercasts.com/ and http://ember101.com/

Ember.js Routing magic - see http://emberjs.com/guides/routing/defining-your-routes/

App.Router.map(function() {
  this.resource('posts', function() {
    this.route('new');
  });
});

creates the following routes:

URL Route Name Controller Route Template
/ index IndexController IndexRoute index
N/A posts1 PostsController PostsRoute posts
/posts posts.index PostsController
PostsIndexController
PostsRoute
PostsIndexRoute
posts
posts/index
/posts/new posts.new PostsController
PostsNewController
PostsRoute
PostsNewRoute
posts
posts/new

Angular.js

See - Angularjs.org and Angular video intros http://www.egghead.io/

Rails Console - Slide 8

console

Rails console in production - rails c RAILS_ENV=production (in dev mode you can leave out the RAILS_ENV part).

Pry

Enhanced Rails console with data introspection and breakpoints etc. - http://railscasts.com/episodes/280-pry-with-rails?view=asciicast

also https://github.com/pry/pry

Admins - Rails Admin, Active Admin, Netzke - Slide 19

Rais Admin

Rails admin - https://github.com/sferik/rails_admin and link to demo http://rails-admin-tb.herokuapp.com/

Active Admin

Active Admin - http://activeadmin.info/, Github https://github.com/gregbell/active_admin

Netzke

Creates Grids based on ExtJS - http://netzke.org/

Authentication, Authorization - Slide 5

Autentication - Sorcery, Devise

Authentication with Sorcery - https://github.com/NoamB/sorcery, see Wiki install and configuration https://github.com/NoamB/sorcery/wiki

Authentication with Devise - https://github.com/plataformatec/devise, see Wiki https://github.com/plataformatec/devise/wiki

Authorization - CanCan Example

Authorization with CanCan - https://github.com/ryanb/cancan, CanCan Wiki https://github.com/ryanb/cancan/wiki

CanCan Ability model class example:

See also Railscasts episodes on CanCan - http://railscasts.com/episodes/192-authorization-with-cancan?view=asciicast

class Ability
  include CanCan::Ability
  include ApplicationHelper

  #def initialize(user)
    # Define abilities for the passed in user here. For example:
    #
    #   user ||= User.new # guest user (not logged in)
    #   if user.admin?
    #     can :manage, :all
    #   else
    #     can :read, :all
    #   end
    #
    # The first argument to `can` is the action you are giving the user 
    # permission to do.
    # If you pass :manage it will apply to every action. Other common actions
    # here are :read, :create, :update and :destroy.
    #
    # The second argument is the resource the user can perform the action on. 
    # If you pass :all it will apply to every resource. Otherwise pass a Ruby
    # class of the resource.
    #
    # The third argument is an optional hash of conditions to further filter the
    # objects.
    # For example, here the user can only update published articles.
    #
    #   can :update, Article, :published => true
    #
    # See the wiki for details:
    # https://github.com/ryanb/cancan/wiki/Defining-Abilities
  #end

  def initialize(user)
    @user = user || User.new # for guest
    @user.roles.each { |role| send(role.name.to_sym) }
    @organizations = Organization.all

    if @user.roles.size == 0
      can :read, :all #for guest without roles
    end
  end

  def account

    #can [:manage], [Organization, User, CustomerRecord, AdminRecord, Vendor, Product, PriceList, BankRecord, CreditCardRecord, Category, Page, Location, Order, LineItem, OrderTransaction, Access]
    can :manage, :all
    #exceptions_to_rules
  end

  def exceptions_to_rules
    cannot :destroy, Organization
  end

  def management
    can [:read, :create, :update], [Organization, User, CustomerRecord, AdminRecord, Vendor, Product, PriceList, BankRecord, CreditCardRecord, Location, Order, OrderTransaction, Access]
    can [:read], [Category, LineItem]
  end

  def management_division
    can [:read, :create, :update], [Organization, User, CustomerRecord, AdminRecord, Vendor, Product, PriceList, BankRecord, CreditCardRecord, Category, Location, Order, LineItem, OrderTransaction, Access]
    can [:read], [Category, LineItem]
  end

  def staff
    can [:read, :create, :update], [User, CustomerRecord]
    can [:read], [Organization, Category, PriceList, AdminRecord, Vendor]
  end


end

In user.rb model you create the helper method role?, which helps pass in the associated roles table by collecting the role names (many-to-one relationship between Role and User), which ability.rb then refers to in the initialize method dynamically for each user (using send() to turn role name strings into methods, e.g. def management def staff etc.).

 def role?(role)
    roles.map {|s| s.name}.include? role.to_s
  end

Finally (taken from Railscast Article), if you refer (with logged in user) with the can? view helper method to the user’s abilities, it filters for allowed operations (which can be any operation which has been defined in CanCan’s ability class, not only CRUD operations - create, read, update, delete). CanCan automatically attaches abilities to the controllers. Example of view helper using CanCan:

<p>
  <% if can? :update, @article %>
    <%= link_to "Edit", edit_article_path(@article) %> |
  <% end %>
  <% if can? :destroy, @article %>
    <%= link_to "Destroy", @article, :method => :delete, :confirm => "Are you sure?" %> |
  <% end %>
  <%= link_to "Back to Articles", articles_path %>
</p>

Active Record Finder - Slide 7

Where

See Rails Guides article - http://guides.rubyonrails.org/active_record_querying.html

Search scopes

Search scopes (Devmynd Article) - http://www.devmynd.com/blog/2013-3-effective-rails-part-2-hiding-activerecord

Hiding Actice Record behind search scopes:

# app/models/order.rb
class Order < ActiveRecord::Base

  def self.recent_open_orders_for_customer(customer, page, size=20)
    open.for_customer(customer).by_recency.paged(page, size).to_a
  end

  scope :open, ->{ where(status: "open") }
  scope :for_customer, ->(customer) { where(customer: customer) }
  scope :by_recency, ->{ order("created_at DESC") }
  scope :paged, ->(page, size=20) { offset(size).limit(size) }

end

# app/controllers/orders_controller.rb
class OrdersController < ApplicationController

  def index
    @orders = Order.recent_open_orders_for_customer(
      current_customer, params[:page], params[:page_size])
  end

end

Active Record Associations - Slide 10

has_many, belongs_to, has_many :through

acts_as_tree - https://github.com/amerine/acts_as_tree, a bit dated but still functional http://railscasts.com/episodes/162-tree-based-navigation?view=asciicast

awesome_nested_set (nested set) - https://github.com/collectiveidea/awesome_nested_set/

polymorphic associations

many_to_many with behaviour

STI - single table inheritance

see ‘Rails Recipes’ book, or ‘Rails Cookbook’ (contain many of these examples)

Storing Flexible Data, NoSQL - Slide 11

Postgres Hstore - see DevMynd article http://www.devmynd.com/blog/2013-3-single-table-inheritance-hstore-lovely-combination

Active Record Postgres hstore gem - https://github.com/engageis/activerecord-postgres-hstore

hstore gem is built into Rails 4

Serializing flexible attributes into Rails, built into Rails 3.

Referring to PDF doc ‘Postgres Experts - Simplifying DB Design’ - simple_db.pdf

Concepts from PDF:

EAV tables (‘Eavil table’) data blob (‘e-blob’)

MongoDB - no transactions

Faking out transactions in MongoDB - http://blog.mongodb.org/post/7494240825/master-detail-transactions-in-mongodb

Postgresql 9.3 - FDW (integrating Redis, MongoDB)

PostgreSQL 9.3 beta: Federated databases and more - http://lwn.net/Articles/550418/

See ‘Linking PostgreSQL to Redis’ in article.

PostGresql new features

links - https://postgres.heroku.com/blog/past/2012/3/14/introducing_keyvalue_data_storage_in_heroku_postgres/, Postgres the bits you haven’t found, Embracing the Web with JSON and V8, NoSQL in Postgres intro

Rails Asset Pipeline, Twitter Bootstrap Framework - Slide 17

Asset Pipeline

Rails Guides to Asset Pipeline - http://guides.rubyonrails.org/asset_pipeline.html

Integrating Bootstrap, Less, SASS

Twitter Bootstrap - http://twitter.github.io/bootstrap/

Less - http://lesscss.org/

SASS - http://sass-lang.com/

Handling Data, CSV, Excel, PDF generation - Slide 16

CSV Import

Note - db/seeds.rb is the preferred way to create db data e.g. rake db:seed (or rake db:seed RAILS_ENV=production), also http://edgeguides.rubyonrails.org/migrations.html#migrations-and-seed-data

CSV import - create a load_data.rake task in lib/tasks directory of Rails app. Example of complex load task (one could split these up into separate rake tasks) - execute task with rake db:load:load_data:

# Provide tasks to load and delete data.
require 'active_record'
require 'active_record/fixtures'

namespace :db do
  DATA_DIRECTORY = File.join(Rails.root, "lib", "tasks", "load_csv")
  namespace :load_data do 
    TABLES = %w(categories products)
    #MIN_USER_ID = 100    # Starting user id for the sample data
  
    desc "Load data"
    task :load => :environment do |t|
      require 'csv'
      
      CSV.foreach("lib/tasks/load_csv/categories.csv", :headers => true) do |row|
        Category.create(:name => row['name'], :description => row['description'], :created_at => Time.now, :updated_at => Time.now)
      end
      puts "categories o.k."
      
      CSV.foreach("lib/tasks/load_csv/products.csv", :headers => true, :col_sep => ",") do |row|
        Product.create(:organization_id => '1', :vendor_id => '1', :category_id => '1', 
        :made_in_canada => false, :available => row['available'], :manufacturer_nr => row['manufacturer_nr'],
        :created_at => Time.now, :updated_at => Time.now)
      end
      puts "products o.k."
      
    end
      
    desc "Remove data" 
    task :delete => :environment do |t|
      TABLES.each do |t|
        klass = t.classify.constantize
        #id = klass == User ? "id" : "user_id"
        #klass.delete_all("#{id} >= #{MIN_USER_ID}")
        klass.delete_all
      end
    end
  end

end

It reads in various CSV files in the lib/tasks/load_csv directory, e.g. categories.csv:

name,description
garden,garden products
bath,bath products

PDF generation

Prawn - http://prawn.majesticseacreature.com/ and https://github.com/prawnpdf/prawn

Or PDFKit - https://github.com/pdfkit/pdfkit, also PDF toolkit (large collections of PDFs) - http://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/

Simple example with Prawn:

require 'prawn'

Prawn::Document.generate('hello.pdf') do |pdf|
  pdf.text("Hello Prawn!")
end