@ Rakuten Taiwan weekly meetup
Answer for questions last week
Understanding ActionView
Understanding ActionController
rails w/ redis
rails w/ puma
rails-webpack
rails generate scaffold User name:string nickname:string account:references
Answer: Use `references` keyword w/ scaffold
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: Use ActiveRecord callbacks.
(1) before_validation
(2) after_validation
(3) before_save
(4) before_create
(5) after_create
(6) after_save
(7) after_commit
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:
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
app/views/ people / index .html .erb
Naming Convention
Controller Name
Action Name
ERB will see this extension to generate corresponding file
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
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>
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 %>.
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>
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
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,
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>
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
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
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
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 |
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
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.
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
# 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
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
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
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,
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
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)