Showing posts with label Rails. Show all posts
Showing posts with label Rails. Show all posts

Tuesday, August 1, 2017

BikeOrderForm

BikeOrderForm

Here is my recent work: (other works)

BikeOrderForm

BikeOrderFrom is a site which demos the major functionality from an on-line ordering page of a bike website.
It consists of an ordering from for choosing models as well as color and size accrodingly.
The color and size attributes are different from model to model. For example, model A might have blue, red and yellow in colors, model B might have blue only. There is no rule for assigning colors and size to each model. This system builds up a form to setup the color and size configurations for each model. Thus, users can find the correct color and size to choose for a certain model.
enter image description here
This demo site utilizes techniques as follows:
The demo site: https://bikeorderform.herokuapp.com/
The code in github: https://github.com/chaoyee/bikeorderform
My blog for coding: http://charles4code.blogspot.tw/

Monday, July 10, 2017

Upgrade Rails From 4.2 to 5.0

Upgrade Rails From 4.2 to 5.0

Please see the reference: Upgrading from Rails 4.2 to Rails 5.0

Ruby version > 2.2.2

  $ rvm get stable
  $ rvm install 5.1.2
  $ rvm --default use 5.1.2 (use 5.1.2 as default and current version)
  $ rvm list

Modify Gemfile

  gem 'rails', '4.2.5.1' -> '5.1.2'
  gem 'coffee-rails'     -> # , '~> 4.1.0' 

Active Record Models Now Inherit from ApplicationRecord by Default.

Create an application_record.rb file in  ```app/models/``` and add the following content:
    class ApplicationRecord < ActiveRecord::Base
      self.abstract_class = true
    end

Then modify all model as:

    class Post < ApplicationRecord
                :
    end

Comment the following line in config/application.rb

#config.active_record.raise_in_transactional_callbacks = true

Change ‘for’ to ‘permit’ in ‘controllers/application_controller.rb’ (if gem devise is used)

class ApplicationController < ActionController::Base
  :
  :
  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:name, :email, :password, :password_confirmation)}
    devise_parameter_sanitizer.permit(:account_update) { |u| u.permit(:name, :email, :password, :password_confirmation, :current_password) }
  end
end

Update Database Migration File

It is necessary to add version information after version information in database migration file after Rails 5. For example, [5.0]is added in the following example:

class CreateOrders < ActiveRecord::Migration[5.0]
  def change
    create_table :orders do |t|
      t.string :po_number
      t.date :shipment_require_date
      t.date :order_date
      t.string :ship_to
      t.text :reference

      t.timestamps
    end
  end
end

Or, we can add a bunch of code to test which version of Rails and add the information to the end of class declaration. Likewise:

migration_superclass = if ActiveRecord::VERSION::MAJOR >= 5
  ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"]
else
  ActiveRecord::Migration
end

And also change ActiveRecord::Migration to migration_superclass.

class CreateOrders < migration_superclass

Update gem spring

System shows warning message while execute bundle install:

Array values in the parameter to `Gem.paths=` are deprecated.
Please use a String or nil.

The issue has been posted here.

The solution is to update gem spring:

bundle update spring && bundle exec spring binstub --remove --all && bundle exec spring binstub --all

Thursday, October 13, 2016

Using Gem 'wicked_pdf' to Generate PDF FIle in Rails

Using Gem 'wicked_pdf' to Generate PDF FIle in Rails
Ok, it ‘s turn for PDF file in Rails.
The wicked_pdf is the best gem I have seen for generating pdf file. It is powerful, versatile and flexible. It also provides the capability of generate PDF from HTML file. This is just what I want.
Thewicked_pdf utilizing the shell utility wkhtmltopdf to serve a PDF file to a user from HTML.
There are also many options which provides huge flexibility to users to customize the output PDF file.

Installation

put it in Gemfile
gem 'wicked_pdf'
Likewise, bundle install. Then, execute the initializer.
$rails generate wicked_pdf
It will create app/config/initializers/wicked_pdf.rb. Normally, nothing has to do with it if higher version of Rails was used. (My Rails version is 5)
Because wicked_pdf is a wrapper for wkhtmltopdf, you’ll need to install that in Gemfile, too.
gem 'wkhtmltopdf-binary'
Of course, then bundle install.

Usage

1.Put the code in the response_tosection of the controller. Like:
def show
  @orderdetails = @order.orderdetails.includes(:model, :size, :color)
  respond_to do |format|
    fn = "order_#{@order.po_number}_#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}"
    format.pdf { render pdf: fn, disposition: 'attachment'}
  end
end
2.Copy the show.html.erb to a new file and rename it as show.pdf.erb.
3. Remove all the link_to tags in the file to prevent them from showing in the pdf file.
It’s done!
Further reading: How To Create PDFs in Rails

Using Gem 'to_spreadsheet' to Generate XLSX FIle in Rails

Using Gem 'to_spreadsheet' to Generate XLSX FIle in Rails

Now, let’s find out how to generate Excel file in Rails.

After searching many gems, I found that axlsx is a very well-designed for my purpose. However, it generates xlsx file by building up the components piece by piece. I need a quick solution which can generates a xlsx file from html file. Then I found a gem called to_spreadsheet that is totally full-filled my needs!

Actually, to_spreadsheet is a wrapper of axlsx. It simplifies the usage and allows us using the existing view template to generate xlsx file.

Installation

Put it in Gemfile.

gem 'to_spreadsheet'

Likewise, bundle install.

Usage

1.Put the code in the response_tosection of the controller. Like:

def show
  @orderdetails = @order.orderdetails.includes(:model, :size, :color)
    respond_to do |format|
    fn = "order_#{@order.po_number}_#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}"

    format.xlsx { render xlsx: :show, filename: fn }
  end
end

2.Copy the show.html.erb to a new file and rename it as show.xlsx.erb.
3. Remove all the link_to tags in the file to prevent them from showing in the xlsx file.
4. Try to put the data you need to show in <table></table> block.

Done!

Wednesday, October 12, 2016

Using Service Objects in Rails

Using Service Objects in Rails

If you have many lines of code in your Models or Controllers, you might need to extract the logics out of them and wrap them up to Service Objects

They may be:

  • A bunch of code called by many controllers.
  • Dealing with output or input.
  • A specific part of code containing domain knowledge.

Let us take the CSV file generation of the order details for example.

Create a New Directory ‘app/services’

First, let’s create a new directory ‘services’ under ‘app/’.

$cd app
$mkdir services
$cd services

Create a New File ‘app/services/document_service.rb’

Second, add a new file called ‘document_service.rb’.

Create a New Service Object called ‘DocumentService’

Third, move the code in private action query_to_csv of the orders_controller to the new service object. Now, we call this service object as DocumentService.

class DocumentService
  def initialize(order_id)
    @order_id = order_id
  end

  # Use ActiveRecord::Base.connection.exec_query to query db directly only once.
  def to_csv
    query_string = 'select a.po_number as "PO Number", c.name as "Model Name", d.name as Color,
        e.name as Size, b.price as Price, b.quantity as Quantity,
        (b.price*b.quantity) as "Total Amount"
      from orders a, orderdetails b, models c, colors d, sizes e
      where a.id = b.order_id and b.model_id=c.id and b.color_id=d.id and b.size_id=e.id and b.order_id=' + @order_id.to_s
    results = ActiveRecord::Base.connection.exec_query(query_string)
    CSV.generate(headers: true) do |csv|
      csv << results.columns
      results.rows.each do |row|
        csv << row
      end
    end
  end
end

Modify The Show Action

Four, modify the show action and delete private action query_to_csv.

def show
   :
   :
  format.csv { send_data DocumentService.new(@order.id).to_csv, filename: fn }
  end
end

That’s all! Now the order controller looks nicer and cleaner.

Further readings:

Monday, October 10, 2016

The "Includes" Magic in Rails

The "Includes" Magic in Rails
If there are many associated models needed to be retrieved during a query, it’s time to use ‘includes’ instead of nothing. It will increase your apps performance by reducing the number of queries to database.
Here is the scenario:
Order has_many Orderdetails

Without ‘includes’

The code:
@orderdetails = @order.orderdetails
It generates the log below.
Started GET “/orders/1” for ::1 at 2016-10-10 04:09:07 +0800
Processing by OrdersController#show as HTML
Parameters: {“id”=>”1”}
Order Load (0.3ms) SELECT “orders”.* FROM “orders” WHERE “orders”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Rendering orders/show.html.erb within layouts/application
Orderdetail Load (0.6ms) SELECT “orderdetails”.* FROM “orderdetails” WHERE “orderdetails”.”order_id” = ? ORDER BY model_id, size_id ASC [[“order_id”, 1]]
Model Load (0.4ms) SELECT “models”.* FROM “models” WHERE “models”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Size Load (0.4ms) SELECT “sizes”.* FROM “sizes” WHERE “sizes”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Color Load (0.6ms) SELECT “colors”.* FROM “colors” WHERE “colors”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 4], [“LIMIT”, 1]]
CACHE (0.0ms) SELECT “models”.* FROM “models” WHERE “models”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
CACHE (0.0ms) SELECT “sizes”.* FROM “sizes” WHERE “sizes”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Color Load (0.2ms) SELECT “colors”.* FROM “colors” WHERE “colors”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 2], [“LIMIT”, 1]]
CACHE (0.0ms) SELECT “models”.* FROM “models” WHERE “models”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Size Load (0.1ms) SELECT “sizes”.* FROM “sizes” WHERE “sizes”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 2], [“LIMIT”, 1]]
CACHE (0.0ms) SELECT “colors”.* FROM “colors” WHERE “colors”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 4], [“LIMIT”, 1]]
CACHE (0.0ms) SELECT “models”.* FROM “models” WHERE “models”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
CACHE (0.0ms) SELECT “sizes”.* FROM “sizes” WHERE “sizes”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 2], [“LIMIT”, 1]]
CACHE (0.0ms) SELECT “colors”.* FROM “colors” WHERE “colors”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 2], [“LIMIT”, 1]]
Rendered orders/show.html.erb within layouts/application (105.4ms)
Rendered common/_navbar.html.erb (1.1ms)
Completed 200 OK in 275ms (Views: 236.7ms | ActiveRecord: 5.1ms)
It takes 14 queries and 5.1ms.

With ‘includes’

The code:
@orderdetails = @order.orderdetails.includes(:model, :size, :color)
It generates the log below.
Started GET “/orders/1” for ::1 at 2016-10-10 14:26:43 +0800
ActiveRecord::SchemaMigration Load (0.2ms) SELECT “schema_migrations”.* FROM “schema_migrations”
Processing by OrdersController#show as HTML
Parameters: {“id”=>”1”}
Order Load (0.2ms) SELECT “orders”.* FROM “orders” WHERE “orders”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Rendering orders/show.html.erb within layouts/application
Orderdetail Load (0.2ms) SELECT “orderdetails”.* FROM “orderdetails” WHERE “orderdetails”.”order_id” = ? ORDER BY model_id, size_id ASC [[“order_id”, 1]]
Model Load (0.1ms) SELECT “models”.* FROM “models” WHERE “models”.”id” = 1
Size Load (0.2ms) SELECT “sizes”.* FROM “sizes” WHERE “sizes”.”id” IN (1, 2) ORDER BY name ASC
Color Load (0.2ms) SELECT “colors”.* FROM “colors” WHERE “colors”.”id” IN (4, 2) ORDER BY name ASC
Orderdetail Load (0.2ms) SELECT “orderdetails”.* FROM “orderdetails” WHERE “orderdetails”.”order_id” = ? ORDER BY model_id, size_id ASC [[“order_id”, 1]]
Rendered orders/show.html.erb within layouts/application (84.2ms)
Rendered common/_navbar.html.erb (1.9ms)
Completed 200 OK in 617ms (Views: 560.0ms | ActiveRecord: 3.4ms)
It takes 6 queries and 3.4ms.
The includes method can minimize the queries to the database. Rails generates a single query each time it need the associated model by default. The ‘includes’ method, however, aggregates the queries to the same associated model .
For example, If there are four order details belong to order id ‘1’, Rails generates 12 ( 4 records x 3 associated models ) queries by default. On the other hand, it only takes 3 queries by using the ‘includes’ method.

Conclusion

Add ‘includes’ method for the query with many associated models.

SQL Statement with Single or Double Quotes for PostgerSQL ?

SQL Statement with Single or Double Quotes for PostgerSQL ?

Single Quotes or Double Quotes? That is a Question!

Consider a sql statement:

SELECT model AS 'Model Name' FROM orders

We usually write it in the format below in Ruby on Rails:

query_string = "SELECT model AS 'Model Name' FROM orders"

It works fine for SQLite and MySQL, but fails for PostgreSQL!

The root cause is that PostgreSQL only accepts double-quoted key words!

Solution: use double-quotes instead of single-quotes:

SELECT model AS "Model Name" FROM orders

In Rails, it should be:

query_string = 'SELECT model AS "Model Name" FROM orders'

Note that it turns to be single quotes for wrapping the whole query string.

Sunday, October 9, 2016

Using Ruby CSV Library to Export CSV File in Rails Apps - send_data method and 'exec_query' Approach

Using Ruby CSV Library to Export CSV File in Rails Apps - send_data method and 'exec_query' Approach

N+1 Queries Problem

Since there are 4 relations (order, model, color, size) for model Orderdetail, it causes ‘N+1 Query’ problems for this scenario. Let’s take a look at the console log:
Started GET “/orders/1.csv” for ::1 at 2016-10-08 12:54:14 +0800
ActiveRecord::SchemaMigration Load (0.2ms) SELECT “schema_migrations”.* FROM “schema_migrations”
Processing by OrdersController#show as CSV
Parameters: {“id”=>”1”}
Order Load (0.2ms) SELECT “orders”.* FROM “orders” WHERE “orders”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Orderdetail Load (0.2ms) SELECT “orderdetails”.* FROM “orderdetails” WHERE “orderdetails”.”order_id” = ? ORDER BY model_id, size_id ASC [[“order_id”, 1]]
Order Load (0.1ms) SELECT “orders”.* FROM “orders” WHERE “orders”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Model Load (0.1ms) SELECT “models”.* FROM “models” WHERE “models”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Size Load (0.1ms) SELECT “sizes”.* FROM “sizes” WHERE “sizes”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Color Load (0.3ms) SELECT “colors”.* FROM “colors” WHERE “colors”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 4], [“LIMIT”, 1]]
Order Load (0.1ms) SELECT “orders”.* FROM “orders” WHERE “orders”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Model Load (0.1ms) SELECT “models”.* FROM “models” WHERE “models”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Size Load (0.1ms) SELECT “sizes”.* FROM “sizes” WHERE “sizes”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Color Load (0.1ms) SELECT “colors”.* FROM “colors” WHERE “colors”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 2], [“LIMIT”, 1]]
Order Load (0.1ms) SELECT “orders”.* FROM “orders” WHERE “orders”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Model Load (0.1ms) SELECT “models”.* FROM “models” WHERE “models”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Size Load (0.1ms) SELECT “sizes”.* FROM “sizes” WHERE “sizes”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 2], [“LIMIT”, 1]]
Color Load (0.2ms) SELECT “colors”.* FROM “colors” WHERE “colors”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 4], [“LIMIT”, 1]]
Order Load (0.1ms) SELECT “orders”.* FROM “orders” WHERE “orders”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Model Load (0.1ms) SELECT “models”.* FROM “models” WHERE “models”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
Size Load (0.1ms) SELECT “sizes”.* FROM “sizes” WHERE “sizes”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 2], [“LIMIT”, 1]]
Color Load (0.1ms) SELECT “colors”.* FROM “colors” WHERE “colors”.”id” = ? ORDER BY name ASC LIMIT ? [[“id”, 2], [“LIMIT”, 1]]
Rendering text template
Rendered text template (0.0ms)
Sent data order_1234_2016-10-08 12:54:14.csv (14.0ms)
Completed 200 OK in 149ms (Views: 13.8ms | ActiveRecord: 4.7ms)
There are four order details for order 1, it takes 19 queries for 4.7ms. We can conclude that:
query number = ( number of order details X 4 ) + 3
Thus, the more order details contained in one order, the more query time used.

Direct SQL Query

Is there a way to solve this problem? What if we just use one single query to get all the data we need? Here is my plan. I wrote a single SQL statement which can pull out all the data from the database:
select a.po_number as ‘PO Number’, c.name as ‘Model Name’,
d.name as ‘Color’, e.name as ‘Size’, b.price as ‘Price’, b.quantity as Quantity (b.price*b.quantity) as ‘Total Amount’
from orders a, orderdetails b, models c, colors d, sizes e
where a.id = b.order_id and b.model_id=c.id and b.color_id=d.id and b.size_id=e.id and b.order_id=a.id
I tested it through a standalone database client and it returned the correct result. Then, I called ‘“exec_query” method to do the one-query action in the orders controller. The class used is:
ActiveRecord::Base.connection.exec_query([query string])
The result set is an ActiveRecord::Result object and very easy to handle with. The header and values of the result set can be obtained by using the following methods:
# Get the column names of the result:
result.columns
# => ["id", "model", "color"]

# Get the record values of the result:
result.rows
# => [[1, "model_1", "color_1"],
      [2, "model_2", "color_2"],
      ...
     ]
All we need to do is to save the header and the values to the csv string. All these code was wrapped into a private action query_to_csv(). In action show, the csv string is passed into send_data which outputs the result to the file in csv format.
format.csv { send_data query_to_csv(@order.id), filename: fn }
It’s quite simple. Here is the code in the controller.
app/controllers/orders_controller.rb
class OrdersController < ApplicationController
    :
    :
  def show
    @orderdetails = @order.orderdetails
    add_breadcrumb @order.id, order_path

    respond_to do |format|
      format.html
      format.json
      fn = "order_#{@order.po_number}_#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}.csv"
      # Send_data method and exec_query approach
      format.csv { send_data query_to_csv(@order.id), filename: fn }
    end
  end   

  private

  # Use ActiveRecord::Base.connection.exec_query to 
  # query db directly only once.
  def query_to_csv(order_id)
    query_string = 'select a.po_number as "PO Number", 
        c.name as "Model Name", d.name as Color,
        e.name as Size, b.price as Price, 
        b.quantity as Quantity,
        (b.price*b.quantity) as "Total Amount"
      from orders a, orderdetails b, models c, 
        colors d, sizes e
      where a.id = b.order_id and b.model_id=c.id and
        b.color_id=d.id and b.size_id=e.id and
        b.order_id=' + order_id.to_s
    results = ActiveRecord::Base.connection.exec_query(query_string)       
    CSV.generate(headers: true) do |csv|
      csv << results.columns
      results.rows.each do |row|
        csv << row
      end
    end
  end
end
Note that PostgreSQL Server only allow double quote for the sql statement. So I use single quote for quoting the whole query string and double quote for the item name with space in it. For example:
‘select a.po_number as “PO Number”…………….. ‘
That’s it!

Performance Improvement

Let’s take a look at the console log.
Started GET “/orders/1.csv” for ::1 at 2016-10-08 12:58:50 +0800
ActiveRecord::SchemaMigration Load (0.2ms) SELECT “schema_migrations”.* FROM “schema_migrations”
Processing by OrdersController#show as CSV
Parameters: {“id”=>”1”}
Order Load (0.2ms) SELECT “orders”.* FROM “orders” WHERE “orders”.”id” = ? LIMIT ? [[“id”, 1], [“LIMIT”, 1]]
(0.2ms) select a.po_number as ‘PO Number’, c.name as ‘Model Name’, d.name as ‘Color’, e.name as ‘Size’, b.price as ‘Price’, b.quantity as Quantity, (b.price*b.quantity) as ‘Total Amount’ from orders a, orderdetails b, models c, colors d, sizes ewhere a.id = b.order_id and b.model_id=c.id and b.color_id=d.id and b.size_id=e.id and b.order_id=1
Rendering text template
Rendered text template (0.0ms)
Sent data order_1234_2016-10-08 12:58:50.csv (4.5ms)
Completed 200 OK in 56ms (Views: 4.3ms | ActiveRecord: 1.3ms)
It takes 3 queries for 1.3ms! More than three times faster!
Note: the private action can be extracted as a Service Object. Please refer to this article.

Thursday, October 6, 2016

Using Ruby CSV Library to Export CSV File in Rails Apps - send_data method Approach

Using Ruby CSV Library to Export CSV File in Rails Apps - send_data method Approach

‘send_data’ method approach

As we mentioned in the previous article, there are two approaches to generate CSV file. Here is the other approach: ‘send_data’ method approach.
The ‘send_data’ method approach use ‘send_data’ method to export data directly to the CSV file in the controller. It does not use the template file, instead, the ‘to_csv’ method is created in the object model. The ‘to_csv’ method utilizes CSV commands to generate data. Then ‘send_data’ method use object.to_csvto export data to the CSV file.
Again, we need to require CSV library in application.rb
app/config/application.rb
require 'csv'  <== add this line
require 'rails/all'
  :
For example, I need a download button on the show page of the model Order. First, I have to add code to the show action of the order controller. ( Actually I manipulate @orderdetails is orders controller )
app/controllers/orders_controller.rb
def show
     :
     :
  respond_to do |format|
    format.html
    fn = "order_#{@order.po_number}_#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}.csv"
    format.csv { send_data @orderdetails.to_csv, filename: fn }
  end
end
Second, a new method .to_csv should be added in Orderdetail model.
app/models/orderdetail.rb
class Orderdetail < ApplicationRecord
    :
    :
  def po_number
    self.order.po_number
  end

  def m_name
    self.model.name
  end

  def s_name
    self.size.name
  end

  def c_name
    self.color.name
  end

  def self.to_csv
    header = ['PO Number','Model Name', 'Size', 'Color', 'Price (USD)', 'Quantity', 'Total Amount']

    attributes = %w{ po_number model_name 
      size_name color_name price quantity 
      total_amount}

    CSV.generate(headers: true) do |csv|
      csv << header

      all.each do |orderdetail|
        csv << attributes.map{ |attr| orderdetail.send(attr) }
      end
    end  
  end
    :
    :  
end
Note that %w(aaa bbb) is a shortcut for [“aaa”, “bbb”]. Meaning it’s a notation to write an array of strings separated by spaces instead of commas and without quotes around them.
The CSV library generates data according to the attributesarray to the csvobject. Then pass it to the function who calls it ( here is the send_datamethod).
While typing the url below in the browser, it downloads the CSV file instantly.
http://localhost:3000/orders/1.csv
Here is the exported CSV file.
File name: order_1234_2016-10-05 23-38-27.csv
PO Number,Model Name,Size,Color,Price (USD),Quantity,Total
1234,PINEROLO AL SE 0.2 TIG welded Sora 18 Carbon/Alloy Fork,700C X 48CM,Gloss Red,365.42,106,38734.52
1234,PINEROLO AL SE 0.2 TIG welded Sora 18 Carbon/Alloy Fork,700C X 48CM,Gloss Black,365.42,2,730.84
1234,PINEROLO AL SE 0.2 TIG welded Sora 18 Carbon/Alloy Fork,700C X 50CM,Gloss Red,365.42,2,730.84
1234,PINEROLO AL SE 0.2 TIG welded Sora 18 Carbon/Alloy Fork,700C X 50CM,Gloss Black,365.42,2,730.84
The last step is to make a link somewhere in the order show page.
app/views/orders/show.html.erb
<%= link_to 'Export to CSV', order_path(@order, format: :csv) %>
Done!

Using Ruby CSV Library to Export CSV File in Rails Apps - Template Approach

Using Ruby CSV Library to Export CSV File in Rails Apps - Template Approach
When we need to export data to a file in CSV format, we don’t need an extra gem to do so. The standard ruby library has included this functionality. All we need is to require it in application.rb
app/config/application.rb
require 'csv'  <== add this line
require 'rails/all'
  :
Now, I need to have a download button on the show page of the model Order. First, I have to add code to the show action of the order controller.
At this point, there are two ways to do the job. One is template approach, the other is send_data method approach.
(send_data method approach is in another article: here )

Template Approach

Template approach using XXX.csv.erbas a template to generate data to CSV file. Utilization of CSV commands exists in the template file.
app/controllers/orders_controller.rb
def show
     :
     :
  respond_to do |format|
    format.html
    fn = "order_#{@order.po_number}_#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}.csv"
    format.csv do
      headers['Content-Disposition'] = "attachment; filename =" + fn
      headers['Content-Type'] ||= 'text/csv'
    end
  end
end
Second, a new template file show.csv.erb should be added and written in CSV format.
app/views/orders/show.csv.erb
<%- headers = ['PO Number','Model Name', 'Size', 'Color', 'Price (USD)', 'Quantity', 'Total'] -%>
<%= CSV.generate_line headers -%>
<%- @orderdetails.each do |orderdetail| -%>
  <%= CSV.generate_line([
    orderdetail.order.po_number,
    orderdetail.model.name,
    orderdetail.size.name,
    orderdetail.color.name,
    orderdetail.price,
    orderdetail.quantity,
    orderdetail.total_amount
    ]) -%>
<%- end -%>
Note that <%- -%>prevents the app from generating a line break in the CSV file.
While typing the url below in the browser, it downloads the CSV file instantly.
http://localhost:3000/orders/1.csv
Here is the exported CSV file.
File name: order_1234_2016-10-05 23-38-27.csv
PO Number,Model Name,Size,Color,Price (USD),Quantity,Total
1234,PINEROLO AL SE 0.2 TIG welded Sora 18 Carbon/Alloy Fork,700C X 48CM,Gloss Red,365.42,106,38734.52
1234,PINEROLO AL SE 0.2 TIG welded Sora 18 Carbon/Alloy Fork,700C X 48CM,Gloss Black,365.42,2,730.84
1234,PINEROLO AL SE 0.2 TIG welded Sora 18 Carbon/Alloy Fork,700C X 50CM,Gloss Red,365.42,2,730.84
1234,PINEROLO AL SE 0.2 TIG welded Sora 18 Carbon/Alloy Fork,700C X 50CM,Gloss Black,365.42,2,730.84
The last step is to make a link somewhere in the order show page.
app/views/orders/show.html.erb
<%= link_to 'Export to CSV', order_path(@order, format: :csv) %>
Done!
By the way, there are excellent articles about Ruby CSV library, highly recommended!
A Guide to the Ruby CSV Library, Part I
A Guide to the Ruby CSV Library, Part II

Monday, October 3, 2016

Few Tips for Pagination in Rails Apps

Few Tips for Pagination in Rails Apps
When pagination is needed in Rails apps, I always use gem will_paginate. Because it is very simple and just requiring little effort to accomplish my tasks, gem will_paginate become one of my standard gems for every project.
It is simple, but it still need customization to fit my need. For example, the output is not very pretty. The look is not professional.

With Bootstrap Style

What if we use Bootstrap Style to make it a better look? Sure, there is a gem integrates the Twitter Bootstrap pagination component with will_paginate. It is will_paginate-bootstrap.
As usual, add will_paginate-bootstrapin Gemfile:
gem 'will_paginate-bootstrap'
Prepare collection in controller.
def index
  @orders = Order.all.page(params[:page]).per_page(3)
end
Note that the new, shorter page() method is used. It is easier and more intuitive. Then add the line in view template (change @orders to your collection ):
<%= will_paginate(@orders, renderer: BootstrapPagination::Rails %>

Customize the ‘Previous’ and ‘Next’ Tabs

What if we need to replace these two tabs to a simpler way such as ‘<’ and ‘>’?
According to the API documentation, we can assign these options :previous_label and :next_label to the characters we prefer. Therefore, the code becomes:
<%= will_paginate(@orders, 
  renderer: BootstrapPagination::Rails, 
  previous_label: '<', 
  next_label: '>' 
%>
The result:
Bootstrap Pagination

One More Thing: Helper Method

Note that there are many models shown with pagination. The code is duplicated in every view template. Once again, we need code refactoring. I make a helper method generate_pagination in application_helper.rb :
def generate_pagination(object_handled)
  object_paginated = will_paginate(
    object_handled, 
    renderer: BootstrapPagination::Rails,
    previous_label: '<', next_label: '>')
  object_paginated.html_safe if object_paginated
end
Note that the object_paginated is nil (no pagination shown) while there is only one page in the view. A check of object_paginated is necessary for preventing error.
And the view template:
<%= generate_pagination(@orders) %>
That’s it!

Saturday, October 1, 2016

Making breadcrumbs navigation in Rails Apps

Making breadcrumbs navigation in Rails Apps
Making breadcrumbs in rails apps is quite simple. By using gem breadcrumbs_on_rails, breadcrumbs show up right away.
Only Master-details navigation needed to be carefully designed in order to reveal the relationship between the two models. Please follow the Tips below.

Installation

Add this line to Gemfile:
gem "breadcrumbs_on_rails"
then:
bundle install

Controller Setting

Add code below to the controller
class OrdersController < ApplicationController
  add_breadcrumb 'home', :root_path
  add_breadcrumb 'orders', :orders_path

  def show
    @orderdetails = @order.orderdetails
    add_breadcrumb @order.id, order_path
  end
end  
In most cases, adding breadcrumbs navigation only to ‘index’, show’ and ‘edit’ page.

Add Breadcrumbs in View

And then, add render_breadcrumbs helper to the application view file.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    :
  </head>
  <body>
    <div class="container" >
         :
      <%= render_breadcrumbs %>
      <%= yield %>
    </div>
  </body>
</html>
And it’s done!

Tips

Put ‘home’ navigation code in ApplicationController

The ‘home’ navigation need to be shown in every page of the app. So the code of breadcrumb is necessary to move from all controllers to application controller.
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  add_breadcrumb 'home', :root_path
end

Using customized separator

I use “/” instead of “>>” as the separator. Just assign the separator as follows: (in Bootstrap style)
<%= render_breadcrumbs tag: :li, separator: "" %>

Master-Details navigation

The breadcrumb navigation should show the master-detail relationship between two models. For example, a order may have many order details. The navigation should be something like this:
home / orders / 1 / orderdetails / 2 
The solution is to add three lines of code in action ‘edit’ of the controller for showing the last three breadcrumb above.
def edit
  @order = Order.find(@orderdetail.order_id)
  add_breadcrumb @order.id, order_path(@order)
  add_breadcrumb 'orderdetails', order_path(@order)
  add_breadcrumb @orderdetail.id, edit_order_orderdetail_path
end

Twitter Bootstrap Style

Add class='breadcrumb' and it’s done!
<div class='breadcrumb'>
  <%= render_breadcrumbs tag: :li, separator: "" %>
</div>

Saturday, September 24, 2016

Using bootstrap-datepicker in Rails Apps

Using a datepicker widget in Rails is simple. There are two ways of doing so:


  • Using ‘date_field’ (HTML5)
  • Gem ‘bootstrap-datepicker-rails’

Using 'date_field' to Generate Datepicker Widget

<%= form_for(order) do |f| %>
        :
    <%= f.date_field :shipment_require_date %>
        :
<% end %>       
The datepicker widget will show up while focusing on the field.
However, this HTML5 functionality only supported by Chrome.


It does not work while using Safari or Firefox.

Using Gem‘bootstrap-datepicker-rails’

Gem ‘bootstrap-datepicker-rails’ is a datepicker widget gem in the Bootstrap style for Rails. The document is here.
According to the document, just few steps to go and you are all done!
  • First, include ‘bootstrap-datepicker-rails’ in Gemfile.
gem 'bootstrap-datepicker-rails'
  • And add this line to app/assets/stylesheets/application.css
 *= require bootstrap-datepicker
  • Add this line to app/assets/javascripts/application.js
//= require bootstrap-datepicker
  • add code 
    There are two ways of coding: 
    • jQuery
    • Code In-line

jQuery: Assigning values to the options by using javascript.

<%= f.text_field :shipment_require_date, class: "datepicker" %>
        :
    <script type="text/javascript">
      $(document).ready(function(){
        $('.datepicker').datepicker({
          format: "yyyy/mm/dd",
          autoclose: true,
          todayBtn: "linked",
          todayHighlight': true
        });
      });
    </script>
The story ends.

Code In-line: Assign values to the options directly in-line.

According to the options document, option should be transformed to a certain format (data-date-) in order to be placed in-line to the code.
All options that take a “Date” can handle a Date object; a String formatted according to the given format; or a timedelta relative to today, eg “-1d”, “+6m +1y”, etc, where valid units are “d” (day), “w” (week), “m” (month), and “y” (year). Use “0” as today. There are also aliases for the relative timedelta’s: “yesterday” equals “-1d”, “today” is equal to “+0d” and “tomorrow” is equal to “+1d”.
Most options can be provided via data-attributes. An option can be converted to a data-attribute by taking its name, replacing each uppercase letter with its lowercase equivalent preceded by a dash, and prepending “data-date-” to the result. For example, startDate would be data-date-start-dateformat would bedata-date-format, and daysOfWeekDisabled would be data-date-days-of-week-disabled.
Therefore, option ‘todayBtn’ should be ‘data-date-today-btn‘.
The code of the datepicker part is:
app/views/orders/_form.html.erb
<%= f.text_field :shipment_require_date, 
    data: { provide: "datepicker", 
        'date-format': 'yyyy-mm-dd', 
        'date-autoclose': 'true', 
        'date-today-btn': 'linked',
        'date-today-highlight': 'true'} %>
It generates HTML code as follows:
<input data-provide="datepicker" data-date-format="yyyy-mm-dd" data-date-autoclose="true" data-date-today-btn="linked" data-date-today-highlight="true" type="text" name="order[shipment_require_date]" id="order_shipment_require_date" />
Is this way more concise than the jQuery one? It depends. Maybe the jQuery way is better since the code will be cleaner if there are more than two datepicker widgets.

Making a Helper for Code In-line.

As you can see, if using code in-line method and the datepicker input fields are more than one, it is necessary to refactor the code. A helper is created as follows:
app/helpers/application_helper.rb
def datepicker_field(form, field)
    form.text_field(field, data: { provide: "datepicker",
      'date-format': 'yyyy-mm-dd',
      'date-autoclose': 'true',
      'date-today-btn': 'linked',
      'date-today-highlight': 'true'}).html_safe
end
It will generate the HTML code exactly the same as above.
The code of the datepicker part changes to:
app/views/orders/_form.html.erb
<%= datepicker_field(f, :shipment_require_date) %>

It's nice and clean! Happy ending!