© Stefan Wintermeyer 2018
Stefan WintermeyerLearn Rails 5.2https://doi.org/10.1007/978-1-4842-3489-1_8

8. Cookies and Sessions

Stefan Wintermeyer1 
(1)
Bochum, Germany
 

In this chapter, I’ll talk about cookies and sessions.

Cookies

With a cookie, you can store information on the web browser’s system in the form of strings as key-value pairs that the web server has previously sent to this browser. The information is later sent from the browser to the server in the HTTP header. A cookie (if configured accordingly) is not deleted from the browser system by restarting the browser or by restarting the system. Of course, the browser’s human user can manually delete the cookie.

../images/460214_1_En_8_Chapter/460214_1_En_8_Figa_HTML.gif A browser does not have to accept cookies, and it does not have to save them either. But we live in a world where almost every page uses cookies. So, most users will have the cookie functionality enabled. For more information on cookies, please visit Wikipedia at http://en.wikipedia.org/wiki/Http_cookie .

A cookie has a limited size (the maximum is 4KB). You should remember that the information in the saved cookies is sent from the browser to the server. So, you should use cookies to store only small amounts of data (for example, a customer ID) to avoid the protocol overhead from becoming too big.

Rails provides a hash with the name cookies[] that you can use transparently. Rails automatically takes care of the technological details in the background.

To demonstrate how cookies work, I will show how to build a Rails application that places a cookie on a page, reads it out on another page, and displays the content. The cookie is deleted on a third page.

$ rails new cookie_jar
  [...]
$ cd cookie_jar
$ rails db:migrate
$ rails generate controller home set_cookies show_cookies delete_cookies
  [...]

Populate the controller file app/controllers/home_controller.rb, as shown in Listing 8-1.

class HomeController < ApplicationController
  def set_cookies
    cookies[:user_name]       = "Smith"
    cookies[:customer_number] = "1234567890"
  end
  def show_cookies
    @user_name       = cookies[:user_name]
    @customer_number = cookies[:customer_number]
  end
  def delete_cookies
    cookies.delete :user_name
    cookies.delete :customer_number
  end
end
Listing 8-1

app/controllers/home_controller.rb

Listing 8-2 shows the view file app/views/home/show_cookies.html.erb.

<table>
  <tr>
    <td>User Name:</td>
    <td><%= @user_name %></td>
  </tr>
  <tr>
    <td>Customer Number:</td>
    <td><%= @customer_number %></td>
  </tr>
</table>
Listing 8-2

app/views/home/show_cookies.html.erb

Start the Rails server with rails server and go to the URL http://localhost:3000/home/show_cookies in your browser. You will not see any values, as shown in Figure 8-1.
../images/460214_1_En_8_Chapter/460214_1_En_8_Fig1_HTML.jpg
Figure 8-1

Cookies empty

Now go to the URL http://localhost:3000/home/set_cookies and then back to http://localhost:3000/home/show_cookies. Now you will see the values that you have set in the method set_cookies , as shown in Figure 8-2.
../images/460214_1_En_8_Chapter/460214_1_En_8_Fig2_HTML.jpg
Figure 8-2

Cookies set

By requesting the page http://localhost:3000/home/delete_cookies, you can delete the cookies.

The cookies you have placed in this way stay alive in the browser until you close the browser completely.

Permanent Cookies

Cookies are usually set to give the application a way of recognizing users when they visit again later. Between these visits to the web site, much time can go by, and the user may well close the browser in the meantime. To store cookies for longer than the current browser session, you can use the method permanent. You can expand the previous example by adding the method shown in Listing 8-3 in app/controllers/home_controller.rb.

class HomeController < ApplicationController
  def set_cookies
    cookies.permanent[:user_name]       = "Smith"
    cookies.permanent[:customer_number] = "1234567890"
  end
  def show_cookies
    @user_name       = cookies[:user_name]
    @customer_number = cookies[:customer_number]
  end
  def delete_cookies
    cookies.delete :user_name
    cookies.delete :customer_number
  end
end
Listing 8-3

app/controllers/home_controller.rb

../images/460214_1_En_8_Chapter/460214_1_En_8_Figb_HTML.gif  permanent here does not really mean permanent. You cannot set a cookie permanently. When you set a cookie, it always needs a valid until stamp that the browser can use to automatically delete old cookies. With the method permanent, this value is set to today’s date in 20 years.

Signed Cookies

With normally placed cookies, you have no option on the application side to find out whether the user of the application has changed the cookie. This can quickly lead to security problems because changing the content of a cookie in the browser is no great mystery. The solution is to sign the cookies with a key that is known only to you. This key is automatically created via a random generator with each rails new command and is located in the file config/secrets.yml, as shown in Listing 8-4.

development:
  secret_key_base: f4c3[...]095b
test:
  secret_key_base: d6ef[...]052a
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
Listing 8-4

config/secrets.yml

As mentioned in the comment before the production key , it is not a good idea to store the production key in the source code of your project. It’s better to store it as an environment variable and let the Rails project read it from there.

To sign cookies, you can use the method signed, which you use for writing and reading the cookie. You can expand the previous example by adding the method shown in Listing 8-5 in app/controllers/home_controller.rb.

class HomeController < ApplicationController
  def set_cookies
    cookies.permanent.signed[:user_name]       = "Smith"
    cookies.permanent.signed[:customer_number] = "1234567890"
  end
  def show_cookies
    @user_name       = cookies.signed[:user_name]
    @customer_number = cookies.signed[:customer_number]
  end
  def delete_cookies
    cookies.delete :user_name
    cookies.delete :customer_number
  end
end
Listing 8-5

app/controllers/home_controller.rb

The content of the cookie is now encrypted every time you set the cookie. The user can read the name of the cookie, but not the value.

Sessions

As HTTP is a stateless protocol, you will encounter special problems when developing applications. An individual web page has no connection to the next web page, and they do not even know about one another. But since a user wants to register only once on a web site, not over and over again on each individual page, this can pose a problem. The solution is called a session , and Rails offers sessions to the programmer transparently as with the session[] hash. Rails automatically creates a new session for each new visitor of the web page. This session is saved by default as a cookie, so it is subject to the 4KB limit. You can also store the sessions in the database (see the section “Saving Sessions in the Database”). An independent and unique session ID is created automatically, and the cookie is deleted by default when the web browser is closed.

The beauty of a Rails session is that you can save not only strings there as with cookies, but any object, hashes, and arrays as well. So, you can, for example, use it to conveniently implement a shopping cart in an online shop.

Breadcrumbs via Sessions

As an example, let’s create an application with a controller and three views. When a view is visited, the previously visited views are displayed in a little list.

Here is the basic application:

$ rails new breadcrumbs
  [...]
$ cd breadcrumbs
$ rails db:migrate
$ rails generate controller Home ping pong index
  [...]

First you create a method with which you can save the last three URLs in the session and set an instance variable called @breadcrumbs to be able to neatly retrieve the values in the view. To that end, you set up a before_action in app/controllers/home_controller.rb, as shown in Listing 8-6.

class HomeController < ApplicationController
  before_action :set_breadcrumbs
  def ping
  end
  def pong
  end
  def index
  end
  private
  def set_breadcrumbs
    if session[:breadcrumbs]
      @breadcrumbs = session[:breadcrumbs]
    else
      @breadcrumbs = Array.new
    end
    @breadcrumbs.push(request.url)
    if @breadcrumbs.count > 4
      # shift removes the first element
      @breadcrumbs.shift
    end
    session[:breadcrumbs] = @breadcrumbs
  end
end
Listing 8-6

app/controllers/home_controller.rb

Now you use app/views/layouts/application.html.erb to display the last entries at the top of each page , as shown in Listing 8-7.

<!DOCTYPE html>
<html>
  <head>
    <title>Breadcrumbs</title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <% if @breadcrumbs && @breadcrumbs.any? %>
      <h3>Surf History</h3>
      <ul>
        <% @breadcrumbs[0..2].each do |breadcrumb| %>
          <li><%= link_to breadcrumb, breadcrumb %></li>
        <% end %>
      </ul>
    <% end %>
    <%= yield %>
  </body>
</html>
Listing 8-7

app/views/layouts/application.html.erb

Start the Rails server with rails server and go to http://localhost:3000/home/ping, http://localhost:3000/home/pong, or http://localhost:3000/home/index; at the top you will always see the last three pages that you have visited. Of course, this works only on the second page because you do not yet have a history on the first page you visit.

reset_session

Occasionally, there are situations where you want to reset a session (in other words, delete the current session and start a new, fresh session). For example, if you log out of a web application, the session will be reset. This is easily done, and you can quickly integrate it into your breadcrumb application.

../images/460214_1_En_8_Chapter/460214_1_En_8_Figc_HTML.gif With the switch -s, the generator doesn’t overwrite existing files. In this example, that would be the home_controller.rb file.

$ rails generate controller Home reset -s
Running via Spring preloader in process 49668
        skip  app/controllers/home_controller.rb
       route  get 'home/reset'
      invoke  erb
       exist    app/views/home
      create    app/views/home/reset.html.erb
      invoke  test_unit
        skip    test/controllers/home_controller_test.rb
      invoke  helper
   identical    app/helpers/home_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
   identical      app/assets/javascripts/home.coffee
      invoke    css
   identical      app/assets/stylesheets/home.css

The correspondingly expanded controller , named app/controllers/home_controller.rb, looks like Listing 8-8.

class HomeController < ApplicationController
  before_action :set_breadcrumbs
  def ping
  end
  def pong
  end
  def index
  end
  def reset
    reset_session
    @breadcrumbs = nil
  end
  private
  def set_breadcrumbs
    if session[:breadcrumbs]
      @breadcrumbs = session[:breadcrumbs]
    else
      @breadcrumbs = Array.new
    end
    @breadcrumbs.push(request.url)
    if @breadcrumbs.count > 4
      # shift removes the first element
      @breadcrumbs.shift
    end
    session[:breadcrumbs] = @breadcrumbs
  end
end
Listing 8-8

app/controllers/home_controller.rb

So, you can delete the current session by going to the URL http://localhost:3000/home/reset.

../images/460214_1_En_8_Chapter/460214_1_En_8_Figd_HTML.gif It’s important not just to invoke reset_session, but you need to also set the instance variable @breadcrumbs to nil. Otherwise, the old breadcrumbs would still appear in the view.

Saving Sessions in the Database

Saving the entire session data in a cookie on the user’s browser is not always the best solution. Among other reasons, the limit of 4KB can pose a problem. But it’s no big obstacle; you can relocate the storing of the session from the cookie to the database with the gem at https://github.com/rails/activerecord-session_store . The session ID is of course still saved in a cookie, but the other session data is stored in the database on the server.

To install the gem, you have to add the line shown in Listing 8-9 at the end of the file Gemfile.

gem 'activerecord-session_store'
Listing 8-9

Gemfile

After that, run the bundle install command.

$ bundle install
[...]

Next, you have to run rails generate active_record:session_migration and rails db:migrate to create the needed table in the database.

$ rails generate active_record:session_migration
      create  db/migrate/20150428183919_add_sessions_table.rb
$ rails db:migrate
== 20150428183919 AddSessionsTable: migrating =============================
-- create_table(:sessions)
   -> 0.0019s
-- add_index(:sessions, :session_id, {:unique=>true})
   -> 0.0008s
-- add_index(:sessions, :updated_at)
   -> 0.0008s
== 20150428183919 AddSessionsTable: migrated (0.0037s) ====================

Finally, change the session_store value in the file config/initializers/session_store.rb to :active_record_store, as shown in Listing 8-10.

Rails.application.config.session_store :active_record_store, :key => '_my_app_session'
Listing 8-10

config/initializers/session_store.rb

You’re finished. Start the server again with rails server and Rails will save all sessions in the database .

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.5.183