1 - Custom Rake tasts #66¶ ↑
(1/4/13)
Task dependency syntax is '=>' in task definition line
Requires and runs dependent task before running current task. '[]', ie array, can be used to supply multiple dependencies
2 - Rails 3 Screencasts - ActionDispatch¶ ↑
(11/4/13)
Refactor of ActionController stack
http middleware routing
Nicer syntax for routing cfg
'session#new' = 'session' controller and 'new' action Redirects available direct from routing cfg file Access Rack direct from routing cfg file
3 - Factories not Fixtures #158¶ ↑
(10/5/14)
Make RSpec aware of Factory by adding a 'require' line to '/spec/spec_helper.rb'
Factories are an excellent way to improve the tests in your Rails application
4 - What is new in Rails 4 #400¶ ↑
(11/11/14)
Rails 4 has support for native datatypes in Postgres.¶ ↑
$ rails g scaffold article name content:text published_on:date tags properties:hstore Then alter migration: * Add 'execute "create extension hstore"'. * Add the 'array' option to the ':tags' column and setting it to 'true': ... "t.string :tags, array: true" ...
When creating a new Article, we can pass in a Ruby array for 'tags' which will be converted to a Postgres array.
While for the 'properties' we can pass in a hash.
>> Article.create! name:"Hello", tags: %w[ruby rails], properties: {author:"Eifion"}
We could do something similar by serializing a text column but this approach allows us to interact with these columns, in a much more efficient way!
ActiveRecord features:¶ ↑
-
'all'
Now returns ActiveRecord relation instead of an array.
Lazily-loaded so only triggers database query when necessary
-
'load'
Returns ActiveRecord relations but triggers query (for eg caching behaviour)
-
'not'
We can not call 'not' on the 'where' scope to invert it
>> Article.where.not(name: "Hello") Article Load (107.6ms) SELECT "articles".* FROM "articles" WHERE ("articles"."name" != 'Hello')
-
'find_by'
We’ve always been able to use dynamic finders, such as 'find_by_name', but these rely on method_missing and isn’t very clean. Rails 4 introduces a find_by method that we can pass a hash to, like this:
>> Article.find_by name: "Hello"
There are also new 'find_or_create_by' and 'find_or_inititalize_by' methods and you’re encouraged to use these instead of the other ones.
-
'scope'
Needs to pass in a callable object as a second object eg lambda
class Article < ActiveRecord::Base scope :sorted, -> { order(:name) } end
ActiveModel::Model¶ ↑
-
Acts like ActiveModel object but not persisted to DB
-
Include ActiveModel::Model in the class + create 'attr_accessors' then can interact with it like an ActiveRecord model
>> class Message >> include ActiveModel::Model >> attr_accessor :name, :content >> end => nil >> m = Message.new(name: "foo") => #<Message:0x007ff5dca1e760 @name="foo"> >> m.name => "foo"
It’s especially great if you want to pass a custom class into a 'form_for' call.
Views¶ ↑
-
Helper methods to generate form fields for associations
'collection_select'
'collection_radio_buttons'
'collection_check_boxes'
Generating list of checkboxes useful for many-to-many associations or on array attribute. We pass in the attribute’s name, a collection of values, and a name and id. <div class="field"> <%= f.collection_check_boxes :tags, %w[ruby rails design], :to_s, :to_s %> </div>
-
HTML 5 helper methods eg 'date_select' changed to 'date'
Browser then handles how it's displayed
-
Template handlers
Rename 'edit.html.erb' to 'edit.html.ruby'.
Uses plain Ruby code so useful for JSON formats (not rendering HTML).
-
Cache digest (called 'Russian doll caching')
Routes¶ ↑
-
Addition of 'concerns' help reduce duplication in the routes
Rails 3 '/config/routes.rb'
resources :articles do resources :comments end resources :photos do resources :comments end
Rails 4 '/config/routes.rb'
concern :commentable do resources :comments end resources :articles, concerns: :commentable resources :photos, concerns: :commentable
This can be really useful if we have a complicated route set with a lot of APIs with duplication between them.
For simpler examples such as this it may be better to stick with using rested resources, however.
-
'match' method no longer supported
Therefore specify exactly type of request accepted so no wildcard matches
Now use 'get' or 'post' methods (or newly supported 'patch')
-
Route constraints automatically turned into default attributes when we generate the URL
'/config/routes.rb':
get 'foo', to: 'articles#index', constraints: {protocol: "https", subdomain: "test"}
Before, if we called foo_url in our application these restraints wouldn’t be included, but now they are.
>> app.foo_url => "https://test.example.com/foo"
Misc¶ ↑
-
Can pass in a call to 'console' with a block in app config's file. Contents evaluated when load Rails env through console.
console do require 'pry' config.console = Pry end
Need to add 'Pry' to gemfile.
-
Eager-load entire Rails app in production
Makes it thread-safe (previously enabled using an option 'threadsafe!')
'/config/environments/production.rb': config.eager_load = true
-
Can get routing info in development from the '/rails/info' path
5 - Importing CSV and Excel #396¶ ↑
(1/12/14)
form_tag¶ ↑
Use 'form_tag' instead of 'form_for' since don't have object to handle importing yet
Form submitted to a new 'import' action on the 'ProductsController' (set by 'routes' file addition)
Set 'multipart' option so that form can handle file uploads
/app/views/products/index.html.erb <h2>Import Products</h2> <%= form_tag import_products_path, multipart: true do %> <%= file_field_tag :file %> <%= submit_tag "Import" %> <% end %>
Controller action¶ ↑
ProductsController::import
-
Rails stores the uploaded file in the file system while its processed (not CarrierWave or Paperclip gems)
-
Pass uploaded file to a new import method on the Product model
/app/controllers/products_controller.rb def import Product.import(params[:file]) redirect_to root_url, notice: "Products imported." end
-
Create route for new path
/config/routes.rb Store::Application.routes.draw do resources :products do collection { post :import } end root to: 'products#index' end
Importing CSV data¶ ↑
App config file contains “ require 'csv' ” so can use Ruby's built-in CSV library
/app/models/product.rb def self.import(file) CSV.foreach(file.path, headers: true) do |row| Product.create! row.to_hash end end
This will yield to the block for each line of data that’s found.
'headers' option means the first line of data will be expected to hold each column’s name which will be used to name the data.
Product created by passing 'row.to_hash'
As long as the column names map to attributes in Product a new record will be created for each row
Modifying Existing Records¶ ↑
If we had 'id' column in CSV data then could update existing records (rather than just adding new ones)
Then use 'find_by_id' and only update certain attributes (listed by model's 'attr_accessible' list)
/app/models/product.rb def self.import(file) CSV.foreach(file.path, headers: true) do |row| product = find_by_id(row["id"]) || new product.attributes = row.to_hash.slice(*accessible_attributes) product.save! end end Before 'products.csv': name,price,released_on Christmas Music Album,12.99,2012-12-06 Unicorn Action Figure,5.85,2012-12-06 Downloaded existing products: id,name,released_on,price,created_at,updated_at 6,Christmas Music Album,2012-12-06,12.99,2012-12-29 20:55:29 UTC,2012-12-29 20:55:29 UTC 7,Unicorn Action Figure,2012-12-06,5.85,2012-12-29 20:55:29 UTC,2012-12-29 20:55:29 UTC ...
Importing Excel spreadsheets¶ ↑
Roo gem (other gems are available…)
/Gemfile gem 'roo' /config/application.rb require 'iconv'
Need header columns as keys to hash so get array of headers + array of current row + call 'transpose' to alter rows & cols.
Altered: /app/models/product.rb def self.import(file) spreadsheet = open_spreadsheet(file) header = spreadsheet.row(1) (2..spreadsheet.last_row).each do |i| row = Hash[[header, spreadsheet.row(i)].transpose] product = find_by_id(row["id"]) || new product.attributes = row.to_hash.slice(*accessible_attributes) product.save! end end 'original_filename' used on the uploaded file because it’s stored in a temporary file which doesn’t have an extension. 3rd option tells Roo not to raise an exception if the file name doesn't match the type. def self.open_spreadsheet(file) case File.extname(file.original_filename) when '.csv' then Roo::Csv.new(file.path, nil, :ignore) when '.xls' then Roo::Excel.new(file.path, nil, :ignore) when '.xlsx' then Roo::Excelx.new(file.path, nil, :ignore) else raise "Unknown file type: #{file.original_filename}" end end (One issue with this solution is an exception is raised when we try to import files exported from our app, not Excel.)
Validating data - using form_for not form_tag¶ ↑
Use 'form_for' so that we can easily display any validation errors just like we would with any other model.
This model isn’t stored in the database, however, it’s a simple Ruby class.
We use ActiveModel here to simulate ActiveRecord.
See github.com/railscasts/396-importing-csv-and-excel/tree/master/store-with-validations