Rails: Introduction (Week 2)

@ Rakuten Taiwan weekly meetup

Agenda

  • Answer for questions last week
  • Understanding ActionView
  • Understanding ActionController
  • rails w/ redis
  • rails w/ puma
  • rails-webpack

Answer for questions last week

Q: How to use cli to set Primary Key and Foreign Key automatically?

rails generate scaffold User name:string nickname:string account:references
Answer: Use `references` keyword w/ scaffold

Answer for questions last week

Q: How to make a column as `required` column?

class CreatePeople < ActiveRecord::Migration
  def change
    create_table :people do |t|
      t.string :name, :null => false
      t.text :bio
      t.date :birthday

      t.timestamps null: false
    end
  end
end
Answer: Use `:null => false`

Answer for questions last week

Q: How to have transaction b/w multiple tables?

Answer: Use ActiveRecord callbacks.
  • (1) before_validation
  • (2) after_validation
  • (3) before_save
  • (4) before_create
  • (5) after_create
  • (6) after_save
  • (7) after_commit

Answer for questions last week

Q: How to rollback transaction b/w multiple transactions?

Tweet.transaction do
   initial_tweets = get_tweets_in_time_range self.name, (Time.now-1.weeks), Time.now 
initial_tweets.each do |tweet|
    new_tweet = Tweet.new
    ..... 
    new_tweet.created_at = DateTime.strptime tweet.created_at.to_s, '%Y-%m-%d %H:%M:%S %z' 

    new_tweet.save! # you have to add '!', once save failed, it will trigger rolls back.
  end
end
Answer:

Answer for questions last week

Q: What will happen if the last letter of model name is `s`?

1. If model name ends in `s`, when using cli to generate the model, it will generate it with `es` as plural.
ex. `address` => `addresses`
Ref: Routes in Rails with controllers that end in 's'

2. If you use an unique model name, just use ActiveSupport to make Rails know how it should work like.
Ref: Model name ends with S

Understanding ActionView

Template

app/views/  people  /  index  .html  .erb
Naming Convention
Controller Name
Action Name
ERB will see this extension to generate corresponding file

Understanding ActionView

Template (Cont.)

Template Engine
Format Engine
html erb
js erb
json jbuilder
xml builder
We can use different template engine for different format. Or we can use `respond_to` to return the file to client side.
def index
    @people = Person.all
    respond_to do |format|
      format.html
      format.json{ render :json => @person.to_json }
      format.xml { render :xml => @person.to_xml }
    end
end

Understanding ActionView

Layout

Under app/views/layout/***.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>People</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

Understanding ActionView

Layout (Cont.)

What is yield?
1. Use only <%= yield %>
yield is responsible for inserting the contents of each page into the layout.

For example,when you visit `localhost/people/show`, and it will convert the contents of show.html.erb to HTML and then inserts it in place of <%= yield %>. 

Understanding ActionView

Layout (Cont.)

What is yield?
2. Give yield a parameter
When yield receive a parameter, it will find the parameter with `content_for` in erb.
For example,
<html>
  <head>
  <%= yield :head %>
  </head>
  <body>
  <%= yield %>
  </body>
</html>
<% content_for :head do %>
  <title>A simple page</title>
<% end %>
 
<p>Hello, Rails!</p>

Understanding ActionView

Layout (Cont.)

keyword layout
class PeopleController < ApplicationController
  layout "special", :only => :index
  # or use :except to exclude those actions 
end
To be noticed, using string or symbol here is different. 
If using symbol after layout, it will see it as a function name then execute it.
class PeopleController < ApplicationController
  layout :determine_layout

  private
    def determine_layout
      (rand(100)%2 == 0) ? "people" : "login"
    end
end

Understanding ActionView

Partial

Naming convention of partial file should start with underscore, ex. _form.html.erb.

​Use it without underscore, ex.
# In index.html.erb

<% @people.each do |person| %>
  <%= render :partial => "person", :locals => {
    :person => person
  } %>
<% end %>
# In _person.html.erb

<tr>
  <td><%= person.name %></td>
  <td><%= person.bio %></td>
  <td><%= person.birthday %></td>
</tr>

For example,

Understanding ActionView

Partial (Cont.)

More improvement for collection partial
# In index.html.erb

<%= render :partial => 'person', :collection => @people, :as => :person %>
# In _person.html.erb

<tr>
  <td><%= person.name %></td>
  <td><%= person.bio %></td>
  <td><%= person.birthday %></td>
</tr>

Understanding ActionView

Helpers

  • javascript_include_tag
    
  • stylesheet_link_tag
  • auto_discovery_link_tag
  • favicon_link_tag
  • image_tag
  • video_tag
  • audio_tag
  • link_to
  • button_to
  • current_page?(url)
Built-in helper functions
  • simple_format
  • truncate
  • strip_tags
  • strip_links
  • distance_of_time_in_words
  • time_tag
  • number_with_delimiter
  • number_with_precision

Understanding ActionView

Custom Helpers

  • Helper files are under app/helpers/***.rb
  • Helper file is global file, so no matter what its filename is, every controllers, models, and views can access them.
module ApplicationHelper
  def gravatar_url(email)
    gravatar_email = Digest::MD5.hexdigest(email.downcase)
    return "http://www.gravatar.com/avatar/#{gravatar_email}?s=48"
  end
end
<%= image_tag gravatar_url(user.email) %>
class ApplicationController < ActionController::Base
  #...
  helper_method :current_user

  protected

  def current_user
    @current_user = User.find(session[:user_id]) if session[:user_id]
  end
end

Understanding ActionView

Helpers (Cont.)

Q: How to use helpers to return plain html?
Answer:
1. raw
2. html_safe
3. content_tag
def person_profile_link(person)
  str = "<div>" + 
    link_to(person.name, profile_path(person)) +
  "</div>"
  
  str.html_safe # or raw(str)
end
def person_profile_link(person)
  content_tag(:div,
    link_to(person.name, profile_path(person)), 
  :class => 'profile_icon' )
end

Understanding ActionView

Helpers (Cont.)

Q: How to use helpers w/ form?
<%= form_for(@person) do |f| %>
  <% if @person.errors.any? %>
    <div><%= @person.errors.full_messages %></div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :bio %><br>
    <%= f.text_area :bio %>
  </div>
  <div class="field">
    <%= f.label :birthday %><br>
    <%= f.date_select :birthday %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
Useful methods for form 
label text_field text_area
radio_button check_box file_field
select select_date select_datetime
hidden_field submit

Understanding ActionController

  • Naming Convention is {name}_controller.rb, and model name must be plural.
  • Each action is a public method in controller.
  • There are a few ways to know what request contains.
  • action_name
  • cookies
  • headers
  • params: This is a Hash from ActiveSupport::HashWithIndifferentAccess
  • request
  • response
  • session

Basics

Understanding ActionController

Basics (Cont.)

  • After you define a noop action method in Controller, and then you access the url. If the view file doesn't exist, it will throw an error about `Template is missing`.
  • Besides, you can customize what you want to render in action method
render :text => "Hello World" # text, xml, json
render :template => "accounts/index" # a.k.a render "accounts/index"
render :action => "index"
  • If action won't render anything, use `redirect_to` to redirect it to another page.

Understanding ActionController

Session

  • Use like Hash, ex: session[:user_id] = @user.id
  • All session stores use a cookie to store a unique ID for each session. For most stores, this ID is used to look up the session data on the server. This has the advantage of being very lightweight and it requires zero setup in a new application in order to use the session. The cookie data is cryptographically signed to make it tamper-proof. And it is also encrypted so anyone with access to it can't read its contents.
  • Can only store around 4kB of data

Understanding ActionController

Cookies

# Use like Hash
cookies[:name] = "haku"

# Save encoded data
cookies.signed[:name] = "haku"

# Save data always in browser
cookies.permanent[:name] = "haku"

# and, you can even use them together
cookies.permanent.signed[:name] = "haku

Understanding ActionController

rescue_from

Use rescue_from to catch those unknown error exception with your own method
class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordInvalid, :with => :show_error

  protected
  def show_error
    # render error message
  end
end 

Understanding ActionController

detect client device

Q: How can we detect which device clients use in Rails?
class ApplicationController < ActionController::Base
  before_action :detect_browser
  
  private
  
  def detect_browser
    case request.user_agent
    when /iPad/i
      request.variant = :tablet
    when /iPhone/i
      request.variant = :phone
    when /Android/i && /mobile/i
      request.variant = :phone
    when /Android/i
      request.variant = :tablet
    when /Windows Phone/i
      request.variant = :phone
    else
      request.variant = :desktop
    end
  end
end

rails w/ redis

Redis is an extremely fast, atomic key-value store. It allows the storage of strings, sets, sorted sets, lists and hashes. Redis keeps all the data in RAM, much like Memcached but unlike Memcached, Redis periodically writes to disk, giving it persistence.
$ brew install redis
$ redis-server
First,

rails w/ redis

gem 'redis'
In Gemfile
bundle install
$redis = Redis.new(:host => 'localhost', :port => 6379)
In config/initializers/redis.rb
class User < ActiveRecord::Base
  # follow a user
  def follow!(user)
    $redis.multi do
      $redis.sadd(self.redis_key(:following), user.id)
      $redis.sadd(user.redis_key(:followers), self.id)
    end
  end
  
  # unfollow a user
  def unfollow!(user)
    $redis.multi do
      $redis.srem(self.redis_key(:following), user.id)
      $redis.srem(user.redis_key(:followers), self.id)
    end
  end
  # ...
end

rails w/ puma

Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications. Puma is intended for use in both development and production environments.
#!/usr/bin/env puma

environment 'production'
threads 2, 64
workers 4

app_name = ""
application_path = "/home/www/#{app_name}"
directory "#{application_path}/current"

pidfile "#{application_path}/shared/tmp/pids/puma.pid"
state_path "#{application_path}/shared/tmp/sockets/puma.state"
stdout_redirect "#{application_path}/shared/log/puma.stdout.log", "#{application_path}/shared/log/puma.stderr.log"
bind "unix://#{application_path}/shared/tmp/sockets/#{app_name}.sock"
activate_control_app "unix://#{application_path}/shared/tmp/sockets/pumactl.sock"

daemonize true
on_restart do
  puts 'On restart...'
end
preload_app!
  1. Add `webpack-rails` to Gemfile
  2. then bundle install
  3. Create a Procfile to let foreman know how to run server
  4. Check webpack-rails demo repo for details 
# Run Rails & Webpack concurrently
# Example file from webpack-rails gem
rails: bundle exec rails server
webpack: ./node_modules/.bin/webpack-dev-server --config config/webpack.config.js
Procfile (Example)

References

Q & A

Rails: Introduction (Week 2)

By CYB

Rails: Introduction (Week 2)

Rails: Introduction (Week 2) @ Rakuten Taiwan weekly meetup

  • 1,532