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!
-
Add `webpack-rails` to Gemfile
-
then bundle install
-
Create a Procfile to let foreman know how to run server
-
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