Hexa's Blog

Free Image Sharing project

10/06/2017 Projects

Introduction

Sometime in my life, I want to share something stupid anonymously, somewhere to store data anonymously. Imgur is good, but I am not that bad to abuse the Imgur, last year, they close the api to share image anonymously and I dont want to do some shit crawling stuffs or playing around with their website.I write my own image data storage. The project written in elixir with phoenix web framework supporting and foundation. You can access the projet at http://image.hexalink.xyz

This small web application which helps you to share your images easily and quickly. Those uploading images might be stored on the system until my server storage getting full. If the server’s full, images will be removed to save space.

Current Features

  • Uploading image

Next Features

  • User can set time to live for image

SQL self join table and explaination to find min/max values

08/06/2017 SQL

Introduction

It’s general and popular to find min/max value of a column in tables with aggregate functions such as min and max. However, I would like to use vanila way to solve this problem, in addition to explain how does it work with join. The explaination can help you get a abstract of join and how does it work in processing.

Issue

To help you understand how join can help you approach min and max, we need a good example. Now let start. We got alot of people in a company, each of them has more then one assessment during their work time. The question is how to get the latest, newest assessment and the first, oldest assessment. The big question is how to get the latest and the first assessment answers filter by that person and assessment, remember that an assessment can be done many times. Our table will look like:

table named: assessment_answers;
|-------------+-----------+---------------+--------------------+-------------|
| id          | person_id | assessment_id | assessment_answers | inserted_at |
|-------------+-----------+---------------+--------------------+-------------|
| integer, fk | int       | int           | jsonb              | datetime    |
|-------------+-----------+---------------+--------------------+-------------|

Thinking approach

To find min/max value of inserted_at value filtered by the person_id and assessment_id, first we need to find all record filtered by person_id and assessment_id, then we must find a min/value of inserted_at via a loop to comparing one to others inserted_at siblings, one which is smaller than others is the smallest and one which is greater than all others is the greatest. This also can be understand in this way.

  1. If you can find a group named A from a fixed set filtered by some conditions, all elements in this group A can find a greater element from a fix set. On the other hands, other elements which does not belong to the group A cannot find any element which is greater than itself, which also means that those other element are the greatest.
  2. If you can find a group named B from a fixed set filtered by some conditions, all elemments in this group B can find a smaller element from a fix set. On the other hands, other elements which does not belong to the group B cannot find any element which is smaller than itself, which also mean that those elements are the smallest.

Solution

I would like to find the latest/newest assessments of all people. Let do it step by step and analyze:

The over all data set of a person who own id is 1. As you can see, for a single person, there are many assessments has been done by him/her. We have to find out the latest assessment answers of each assessment id. For example: assessment answers with id: 1, 7, 8, 9; the person has finished this assessment_id 2 four times.

select id, person_id, assessment_id, inserted_at from assessment_answers where person_id = 1;

 id | person_id | assessment_id |       inserted_at
----+-----------+---------------+-------------------------
  1 |         1 |             2 | 2016-12-14 04:04:46.477     x
  2 |         1 |             3 | 2016-12-14 04:07:11.96
  3 |         1 |             8 | 2016-12-14 04:07:12.74
  4 |         1 |             5 | 2016-12-14 04:07:13.177
  5 |         1 |             1 | 2016-12-14 04:07:14.053
  6 |         1 |             7 | 2016-12-14 04:07:14.427
  7 |         1 |             2 | 2016-12-16 01:19:25.61      x
  8 |         1 |             2 | 2017-02-24 22:13:12.79      x
  9 |         1 |             2 | 2017-02-25 09:48:03.53      x
 10 |         1 |             4 | 2017-02-14 00:00:00
 11 |         1 |             4 | 2017-02-22 00:00:00
 12 |         1 |             6 | 2017-02-14 00:00:00
(12 rows)

Now, let find a group of element which can find a greater one among the fixed set. The comparing value based on inserted_at column.

select aa.id, aa.person_id, aa.assessment_id, aa.inserted_at, ab.id, ab.person_id, ab.assessment_id 
from assessment_answers aa join assessment_answers ab
on aa.person_id = ab.person_id
and aa.assessment_id = ab.assessment_id
and aa.inserted_at < ab.inserted_at        -> I will explain why we set condition here not under _where_ phrase
where aa.person_id = 1
order by aa.id, ab.id;
 id | person_id | assessment_id |       inserted_at       | id | person_id | assessment_id |      inserted_at       
----+-----------+---------------+-------------------------+----+-----------+---------------+------------------------
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  7 |         1 |             2 | 2016-12-16 01:19:25.61
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  8 |         1 |             2 | 2017-02-24 22:13:12.79  |  9 |         1 |             2 | 2017-02-25 09:48:03.53
 10 |         1 |             4 | 2017-02-14 00:00:00     | 11 |         1 |             4 | 2017-02-22 00:00:00
(7 rows)

The fixed set we are talking about is starting from id 1 -> 12, however, our group contain 1, 7, 8, 10. 1, 7, 8, 10 can find greater element for instance:

 id | person_id | assessment_id |       inserted_at       | id | person_id | assessment_id |      inserted_at       
----+-----------+---------------+-------------------------+----+-----------+---------------+------------------------
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  7 |         1 |             2 | 2016-12-16 01:19:25.61

The inserted_at(id_1) is smaller than inserted_at(id_7). For those elements which does not belong to this group cannot find greater element, because they are greatest ones. The other elements should include 2, 3, 4, 5, 6, 9, 11, 12. The previous query used join phrase as a consequence, it remove all unsatisfied element for condition aa.insrted_at < ab.inserted_at. On the other hands, left join takes satisfied and unsatisfied records.

select aa.id, aa.person_id, aa.assessment_id, aa.inserted_at, ab.id, ab.person_id, ab.assessment_id, ab.inserted_at from assessment_answers aa
left join assessment_answers ab
on aa.person_id = ab.person_id
and aa.assessment_id = ab.assessment_id
and aa.inserted_at < ab.inserted_at
where aa.person_id = 1
order by aa.id, ab.id;
 id | person_id | assessment_id |       inserted_at       | id | person_id | assessment_id |      inserted_at       
----+-----------+---------------+-------------------------+----+-----------+---------------+------------------------
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  7 |         1 |             2 | 2016-12-16 01:19:25.61
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  2 |         1 |             3 | 2016-12-14 04:07:11.96  |    |           |               | 
  3 |         1 |             8 | 2016-12-14 04:07:12.74  |    |           |               | 
  4 |         1 |             5 | 2016-12-14 04:07:13.177 |    |           |               | 
  5 |         1 |             1 | 2016-12-14 04:07:14.053 |    |           |               | 
  6 |         1 |             7 | 2016-12-14 04:07:14.427 |    |           |               | 
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  8 |         1 |             2 | 2017-02-24 22:13:12.79  |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  9 |         1 |             2 | 2017-02-25 09:48:03.53  |    |           |               | 
 10 |         1 |             4 | 2017-02-14 00:00:00     | 11 |         1 |             4 | 2017-02-22 00:00:00
 11 |         1 |             4 | 2017-02-22 00:00:00     |    |           |               | 
 12 |         1 |             6 | 2017-02-14 00:00:00     |    |           |               | 
(15 rows)

Now let check the id of assessment answers records if the id 2, 3, 4, 5, 6, 7, 9, 11, 12 are the greatest. They cannot find any greater elements.

Let finalize our works to extract the greatest element only

select aa.id, aa.person_id, aa.assessment_id, aa.inserted_at, ab.id, ab.person_id, ab.assessment_id, ab.inserted_at from assessment_answers aa
left join assessment_answers ab
on aa.person_id = ab.person_id
and aa.assessment_id = ab.assessment_id
and aa.inserted_at < ab.inserted_at
where aa.person_id = 1
and ab.id is null
order by aa.id, ab.id;
 id | person_id | assessment_id |       inserted_at       | id | person_id | assessment_id | inserted_at 
----+-----------+---------------+-------------------------+----+-----------+---------------+-------------
  2 |         1 |             3 | 2016-12-14 04:07:11.96  |    |           |               | 
  3 |         1 |             8 | 2016-12-14 04:07:12.74  |    |           |               | 
  4 |         1 |             5 | 2016-12-14 04:07:13.177 |    |           |               | 
  5 |         1 |             1 | 2016-12-14 04:07:14.053 |    |           |               | 
  6 |         1 |             7 | 2016-12-14 04:07:14.427 |    |           |               | 
  9 |         1 |             2 | 2017-02-25 09:48:03.53  |    |           |               | 
 11 |         1 |             4 | 2017-02-22 00:00:00     |    |           |               | 
 12 |         1 |             6 | 2017-02-14 00:00:00     |    |           |               | 
(8 rows)

And for short, here it’s final shot to find the latest/newest assessment answers of user(id: 1)

select aa.id, aa.person_id, aa.assessment_id, aa.inserted_at from assessment_answers aa
left join assessment_answers ab
on aa.person_id = ab.person_id
and aa.assessment_id = ab.assessment_id
and aa.inserted_at < ab.inserted_at
where aa.person_id = 1
and ab.id is null
order by aa.id;
 id | person_id | assessment_id |       inserted_at       
----+-----------+---------------+-------------------------
  2 |         1 |             3 | 2016-12-14 04:07:11.96
  3 |         1 |             8 | 2016-12-14 04:07:12.74
  4 |         1 |             5 | 2016-12-14 04:07:13.177
  5 |         1 |             1 | 2016-12-14 04:07:14.053
  6 |         1 |             7 | 2016-12-14 04:07:14.427
  9 |         1 |             2 | 2017-02-25 09:48:03.53
 11 |         1 |             4 | 2017-02-22 00:00:00
 12 |         1 |             6 | 2017-02-14 00:00:00
(8 rows)

To find the smallest value, here is your solution:

select aa.id, aa.person_id, aa.assessment_id, aa.inserted_at from assessment_answers aa
left join assessment_answers ab
on aa.person_id = ab.person_id
and aa.assessment_id = ab.assessment_id
and aa.inserted_at > ab.inserted_at
where aa.person_id = 1
and ab.id is null
order by aa.id;
 id | person_id | assessment_id |       inserted_at       
----+-----------+---------------+-------------------------
  1 |         1 |             2 | 2016-12-14 04:04:46.477
  2 |         1 |             3 | 2016-12-14 04:07:11.96
  3 |         1 |             8 | 2016-12-14 04:07:12.74
  4 |         1 |             5 | 2016-12-14 04:07:13.177
  5 |         1 |             1 | 2016-12-14 04:07:14.053
  6 |         1 |             7 | 2016-12-14 04:07:14.427
 10 |         1 |             4 | 2017-02-14 00:00:00
 12 |         1 |             6 | 2017-02-14 00:00:00

Quesions

Q: Can we place the condition aa.inserted_at < ab.inserted_at under where phrase instead of under lelft join on phrase, and why?
A: No, if we put the condition aa.inserted_at < ab.inserted_at under the where phrase, we can only get a group of not-greatest elements

select aa.id, aa.person_id, aa.assessment_id, aa.inserted_at, ab.id, ab.person_id, ab.assessment_id, ab.inserted_at from assessment_answers aa
left join assessment_answers ab
on aa.person_id = ab.person_id
and aa.assessment_id = ab.assessment_id
where aa.person_id = 1
and aa.inserted_at < ab.inserted_at
order by aa.id, ab.id;  
 id | person_id | assessment_id |       inserted_at       | id | person_id | assessment_id |      inserted_at       
----+-----------+---------------+-------------------------+----+-----------+---------------+------------------------
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  7 |         1 |             2 | 2016-12-16 01:19:25.61
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  8 |         1 |             2 | 2017-02-24 22:13:12.79  |  9 |         1 |             2 | 2017-02-25 09:48:03.53
 10 |         1 |             4 | 2017-02-14 00:00:00     | 11 |         1 |             4 | 2017-02-22 00:00:00
(7 rows)

Q: If I use left join in this query, why dont I get a combine of satisfied and unsatisfied records:
A: This is how the query look like and its result set.

select aa.id, aa.person_id, aa.assessment_id, aa.inserted_at, ab.id, ab.person_id, ab.assessment_id, ab.inserted_at from assessment_answers aa
left join assessment_answers ab
on aa.person_id = ab.person_id
and aa.assessment_id = ab.assessment_id
where aa.person_id = 1
order by aa.id, ab.id;    
 id | person_id | assessment_id |       inserted_at       | id | person_id | assessment_id |       inserted_at       
----+-----------+---------------+-------------------------+----+-----------+---------------+-------------------------
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  1 |         1 |             2 | 2016-12-14 04:04:46.477
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  7 |         1 |             2 | 2016-12-16 01:19:25.61
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  1 |         1 |             2 | 2016-12-14 04:04:46.477 |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  2 |         1 |             3 | 2016-12-14 04:07:11.96  |  2 |         1 |             3 | 2016-12-14 04:07:11.96
  3 |         1 |             8 | 2016-12-14 04:07:12.74  |  3 |         1 |             8 | 2016-12-14 04:07:12.74
  4 |         1 |             5 | 2016-12-14 04:07:13.177 |  4 |         1 |             5 | 2016-12-14 04:07:13.177
  5 |         1 |             1 | 2016-12-14 04:07:14.053 |  5 |         1 |             1 | 2016-12-14 04:07:14.053
  6 |         1 |             7 | 2016-12-14 04:07:14.427 |  6 |         1 |             7 | 2016-12-14 04:07:14.427
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  1 |         1 |             2 | 2016-12-14 04:04:46.477
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  7 |         1 |             2 | 2016-12-16 01:19:25.61
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  7 |         1 |             2 | 2016-12-16 01:19:25.61  |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  8 |         1 |             2 | 2017-02-24 22:13:12.79  |  1 |         1 |             2 | 2016-12-14 04:04:46.477
  8 |         1 |             2 | 2017-02-24 22:13:12.79  |  7 |         1 |             2 | 2016-12-16 01:19:25.61
  8 |         1 |             2 | 2017-02-24 22:13:12.79  |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  8 |         1 |             2 | 2017-02-24 22:13:12.79  |  9 |         1 |             2 | 2017-02-25 09:48:03.53
  9 |         1 |             2 | 2017-02-25 09:48:03.53  |  1 |         1 |             2 | 2016-12-14 04:04:46.477
  9 |         1 |             2 | 2017-02-25 09:48:03.53  |  7 |         1 |             2 | 2016-12-16 01:19:25.61
  9 |         1 |             2 | 2017-02-25 09:48:03.53  |  8 |         1 |             2 | 2017-02-24 22:13:12.79
  9 |         1 |             2 | 2017-02-25 09:48:03.53  |  9 |         1 |             2 | 2017-02-25 09:48:03.53
 10 |         1 |             4 | 2017-02-14 00:00:00     | 10 |         1 |             4 | 2017-02-14 00:00:00
 10 |         1 |             4 | 2017-02-14 00:00:00     | 11 |         1 |             4 | 2017-02-22 00:00:00
 11 |         1 |             4 | 2017-02-22 00:00:00     | 10 |         1 |             4 | 2017-02-14 00:00:00
 11 |         1 |             4 | 2017-02-22 00:00:00     | 11 |         1 |             4 | 2017-02-22 00:00:00
 12 |         1 |             6 | 2017-02-14 00:00:00     | 12 |         1 |             6 | 2017-02-14 00:00:00
(26 rows)

The condition I am talking here is aa.person_id = ab.person_id and aa.assessment_id = ab.assessment_id

  • Satisfied condition is aa.person_id == ab.person_id and aa.assessment_id == ab.assessment_id
  • Unsatisfied condition is aa.person_id != ab.person_id or aa.assessment_id != ab.assessment_id
    Why the record that are not satisfied exist in the result set. Hmm, that’s a question I am think about. Personally, I think the developers of postgres did make an exception, hardcode perhap if people use equal in the join expression.

How to install Flash for Opera manually

09/11/2016 etc

Since the documentation of Opera has been out of date, this article is all about installing flash plugin manually, I strongly believe that this can help you in any version of Opera or any operating system. In my free time, I have spent time researching on the resouce files of Opera and trace the directory that Opera use to read the flashplayer.so.

$ cat /lib64/opera-developer/resources/pepper_flash_config.json

{
  "PepperFlashPaths" : [
    "/usr/lib/adobe-flashplugin/libpepflashplayer.so",
    "/usr/lib/pepperflashplugin-nonfree/libpepflashplayer.so",
    "/usr/lib/PepperFlash/libpepflashplayer.so",
    "/usr/lib64/PepperFlash/libpepflashplayer.so",
    "/usr/lib/chromium-browser/PepperFlash/libpepflashplayer.so",
    "/usr/lib/chromium/PepperFlash/libpepflashplayer.so",
    "/usr/lib64/chromium-browser/PepperFlash/libpepflashplayer.so",
    "/usr/lib64/chromium/PepperFlash/libpepflashplayer.so",
    "/opt/google/chrome-beta/PepperFlash/libpepflashplayer.so",
    "/opt/google/chrome-unstable/PepperFlash/libpepflashplayer.so",
    "/opt/google/chrome/PepperFlash/libpepflashplayer.so"
  ]
}

As you can guess, we gonna download flash plugin from its official site then extract into any of these directory such as /usr/lib/adobe-flashplugin/. You need to extract two files including libpepflashplayer.so and manifiest.json, it’s a must to copy manifest.json along with the libpepflashplayer.so. Remember to set extracted files with appropriate permission (chmod command).

Phoenix and using multiple layouts

26/01/2016 Phoenix

1. How to specify the layout when render() in controllers

There is an option in render/3 method, source In the below example, I did specify the layout, the layout will be located at @conn.assigns[:layout], so as :id

defmodule ImageQuickShare.ImageController do
  use ImageQuickShare.Web, :controller
  def show(conn, %{"id" => id}) do
    render(conn, "show.html", id: id,
           layout: {ImageQuickShare.ImageView, "empty_layout.html"})
  end
end

The directory which locate layout look like:

templates
├── image
│   ├── empty_layout.html.eex
│   └── show.html.eex
├── layout
│   └── app.html.eex
└── page
    └── index.html.eex

2. Setup a default layout for all method within a controller

We have to use plug :put_layout.

defmodule ImageQuickShare.ImageController do
  use ImageQuickShare.Web, :controller
  plug :put_layout, {ImageQuickShare.ImageView, "empty_layout.html"}  #<--- HERE

  def show(conn, %{"id" => id}) do
    render(conn, "show.html", id: id)
  end
end

3. Get advanced from PLUG.

Because of using plug, we can also specify the defaul layout in router. In route.ex we can define an extra pipeline.

pipeline :empty_layout do
  plug :put_layout, {ImageQuickShare.ImageView, "empty_layout.html"}
end

And then, within scope, add the pipeline via pipe_through. Here is an example.

scope "/", ImageQuickShare do
  pipe_through [:browser, :empty_layout] # Use the default browser stack
  get "/", PageController, :index
  get "/image", ImageController, :show
end

REFERENCE

Tweak to ssh quickly any server

24/01/2016 Linux

To ssh to server, I used to type ssh --flags, the command line too long and replication. Here is a solution reduce the pain.

Go to file ~/.ssh/config, and add the following configuration. If the file is not exist, you can make a new one.

Host server-dev1
Hostname  xxx.xxx.xxx.xxx
Port 1022
User nguyenvinhlinh

Host server-dev2
Hostname  xxx.xxx.xxx.xxx
Port 1023
User nguyenhoangson

Right after that, you can use ssh with server alias name.

$ ssh server-dev1
$ ssh server-dev2

Devise, generate hash password

04/01/2016 Ruby on Rails 4

The question is that you are using devise and you want to get hash password from raw password. Here is your solution:

password = 'the secret password'
new_hashed_password = User.new(:password => password).encrypted_password

Rails, How to set redirect path after devise updating password

22/12/2015 Rails

This post is all about how to change redirect url after devise update password (or any?). By default, devise after change password successfully, it will redirect to the root url. There are step you have to do to change the redirect path.

  1. Make devise controllers Or generate using command rails g devise:controllers user

  2. After generating, because I want to change the redirect url after changing password. The only file I concern is passwords_controller.rb, the method you have to override is after_resetting_password_path_for, within this method, you have to declare your favorite redirect url.

for example:

class User::PasswordsController < Devise::PasswordsController
  protected
  def after_resetting_password_path_for(resource)
    change_password_success_path
    #OR "http://google.com"
  end
end

Rails, configuring view path of controller

22/12/2015 Rails

This is a solution to configure the view path of any controller in Rails web framework. The only method you have to concern is self.controller_path

class User::HomeController < ApplicationController
  def self.controller_path
    "users/home"
  end
end

With this configuration, all view of User::HomeController will be located in app/users/home/

How to add new public directory in Phoenix framework

15/12/2015 Phoenix

In Phoenix web framework, there is a share directory /priv. By default, it will only public css,js,images,fonts directory. However, If you want to share a new directory to save upload image for example, you have to end_point file at

/lib/app_name/end_point.ex . Look at plug Plug.Static, the only: macro declares which directory, files could be public. Simply, you have to append your directory name at only:line.

This is my configuration to add a directory named avatar public. This directory is under /priv/static.

plug Plug.Static,
    at: "/", from: :blog_phoenix, gzip: false,
    only: ~w(avatars css fonts images js favicon.ico robots.txt)
    ### Other no need configuration
end

Your tree should look like below

priv
├── repo
│   ├── migrations
│   └── seeds.exs
└── static
    ├── avatars
    │   ├── abc.text
    │   ├── Screenshot\ from\ 2015-12-04\ 22-31-39.png
    │   ├── Screenshot\ from\ 2015-12-11\ 01-49-29.png
    │   └── Screenshot\ from\ 2015-12-14\ 22-21-56.png
    ├── css
    │   ├── app.css
    │   └── app.css.map
    ├── favicon.ico
    ├── images
    │   └── phoenix.png
    ├── js
    │   ├── app.js
    │   └── app.js.map
    └── robots.txt

Rails, how to export excel files

07/12/2015 Ruby on Rails 4

On the front-end, instead of using Ajax(post, get). I uses window.location

$("#export-quote-button").click(function(){
  var encoded = $.param(getQuoteParams(), true);
  var url = "/quotes/tran_stats/export_search_result?" + encoded;
  window.location = url;
});

On the back-end, I uses a gem named spreadsheet

def export_search_result
    require 'spreadsheet'
    require 'stringio'
    Spreadsheet.client_encoding = 'UTF-8'
    book = Spreadsheet::Workbook.new
    sheet1 = book.create_worksheet
    #YOUR DATA PROCESSOR HERE
    file_name = "abc.xls"
    spreadsheet = StringIO.new
    book.write spreadsheet
    send_data(spreadsheet.string, :filename => file_name,
              :type => "application/vnd.ms-excel")
end