Quantcast
Channel: movebits
Viewing all articles
Browse latest Browse all 3

Presentation - Useful Rails Concepts

$
0
0

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:

classPostSerializer<ActiveModel::Serializerattributes:id,:title,:stringend

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:apidonamespace:v1doresources:productsendend

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

moduleApimoduleV1classProductsController<ApplicationControllerbefore_filter:restrict_accessskip_before_filter:require_login,:only=>[:index,:show]respond_to:jsoncaches_action:index,:showdefindex@products=Product.allrespond_todo|format|format.json{renderjson:@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]}endActiveRecord::Base.include_root_in_json=falseenddefshow@product=Product.find(params[:id])respond_todo|format|format.json{renderjson:@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]}endActiveRecord::Base.include_root_in_json=falseenddefrestrict_accessapi_key=ApiKey.find_by_access_token(params[:access_token])head:unauthorizedunlessapi_keyenddefrestrict_access2authenticate_or_request_with_http_tokendo|token,options|ApiKey.exists?(access_token:token)endendendendend

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:

defupdate@order=Order.find(params[:id])respond_todo|format|if@order.update_attributes(params[:order])format.html{redirect_to@order,notice:'Order was successfully updated.'}format.json{head:no_content}elseformat.html{renderaction:"edit"}format.json{renderjson:@order.errors,status::unprocessable_entity}endendend

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:

defsignupparams[: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):

classProductDetail<ActiveRecord::Baseattr_accessible:ProductId,:ProductName,:ProductUPCCodeend

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:blogsdoresources:postsendresources:postsdoresources:commentsend

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):

<scripttype="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');returnfalse;});});</script>

Then in the home_controllerarticles 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):

defarticlescategory_selected=params[:cat]sort_by=params[:sort]#do some magic with the params...respond_todo|format|format.htmlformat.jsendend

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><ahref ="/articles/<%= article.id %>"><imgsrc="/assets/note.png"/></a></td><td><h2><ahref="/articles/<%= article.id %>"><strong><%= article.name %> - <%= h truncate(article.description_en, :length => 80) %></strong></a></h2><pclass="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:

URLRoute NameControllerRouteTemplate
/indexIndexControllerIndexRouteindex
N/Aposts1PostsControllerPostsRouteposts
/postsposts.indexPostsController
PostsIndexController
PostsRoute
PostsIndexRoute
posts
posts/index
/posts/newposts.newPostsController
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

classAbilityincludeCanCan::AbilityincludeApplicationHelper#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#enddefinitialize(user)@user=user||User.new# for guest@user.roles.each{|role|send(role.name.to_sym)}@organizations=Organization.allif@user.roles.size==0can:read,:all#for guest without rolesendenddefaccount#can [:manage], [Organization, User, CustomerRecord, AdminRecord, Vendor, Product, PriceList, BankRecord, CreditCardRecord, Category, Page, Location, Order, LineItem, OrderTransaction, Access]can:manage,:all#exceptions_to_rulesenddefexceptions_to_rulescannot:destroy,Organizationenddefmanagementcan[:read,:create,:update],[Organization,User,CustomerRecord,AdminRecord,Vendor,Product,PriceList,BankRecord,CreditCardRecord,Location,Order,OrderTransaction,Access]can[:read],[Category,LineItem]enddefmanagement_divisioncan[:read,:create,:update],[Organization,User,CustomerRecord,AdminRecord,Vendor,Product,PriceList,BankRecord,CreditCardRecord,Category,Location,Order,LineItem,OrderTransaction,Access]can[:read],[Category,LineItem]enddefstaffcan[:read,:create,:update],[User,CustomerRecord]can[:read],[Organization,Category,PriceList,AdminRecord,Vendor]endend

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 managementdef staff etc.).

defrole?(role)roles.map{|s|s.name}.include?role.to_send

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.rbclassOrder<ActiveRecord::Basedefself.recent_open_orders_for_customer(customer,page,size=20)open.for_customer(customer).by_recency.paged(page,size).to_aendscope: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.rbclassOrdersController<ApplicationControllerdefindex@orders=Order.recent_open_orders_for_customer(current_customer,params[:page],params[:page_size])endend

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:dbdoDATA_DIRECTORY=File.join(Rails.root,"lib","tasks","load_csv")namespace:load_datadoTABLES=%w(categories products)#MIN_USER_ID = 100    # Starting user id for the sample datadesc"Load data"task:load=>:environmentdo|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)endputs"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)endputs"products o.k."enddesc"Remove data"task:delete=>:environmentdo|t|TABLES.eachdo|t|klass=t.classify.constantize#id = klass == User ? "id" : "user_id"#klass.delete_all("#{id} >= #{MIN_USER_ID}")klass.delete_allendendendend

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

Viewing all articles
Browse latest Browse all 3

Trending Articles