Presentation - Useful Rails Concepts
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:
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 | posts 1 |
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