You are on page 1of 47

Ruby on Rails Pitfall

Or just stupid mistakes we made
Robin Lu IN-SRC Studio robinlu@in-src.com RubyConfChina2009

Friday, May 22, 2009

IN-SRC Studio
• http://www.in-src.com • Team behind Caibangzi.com • Full stack Ruby On Rails Development • Projects from Pepboys,Vitality, Healthwise...
Friday, May 22, 2009

‘and’ or ‘&&’
What does this mean?
result = func(arg) and render(:text => result)

Friday, May 22, 2009

‘and’ or ‘&&’
What does this mean?
result = func(arg) and render(:text => result)

Why not this?
result = func(arg) && render(:text => result)

Friday, May 22, 2009

‘and’ or ‘&&’
What does this mean?
result = func(arg) and render(:text => result)

Why not this?
result = func(arg) && render(:text => result)

Be aware of the operator precedence

Friday, May 22, 2009

strip_tags
Display user input text without tags

What we did:

Friday, May 22, 2009

strip_tags
When
text = ‘<img title="http://example.com/x.js?" src="#"’

the page becomes:
<p> <img title="http://example.com/x.js?" src="#" </p>

Friday, May 22, 2009

strip_tags
strip_tags is not safe by itself h strip_tags(text)

Friday, May 22, 2009

cache
class Blog1Controller < ApplicationController def list unless read_fragment(:action => 'list') @articles = Article.find_recent end end end <% cache do %> <ul> <% for article in @articles -%> <li><p><%= h(article.body) %></p></li> <% end -%> </ul> <% end %>
Friday, May 22, 2009

Controller

list.html.erb

cache
Result: sometime got crash due to uninitialized @articles

Friday, May 22, 2009

cache
article list

Friday, May 22, 2009

cache
article list check cache

Friday, May 22, 2009

cache
article list check cache
list

Friday, May 22, 2009

cache
article list check cache render
list

Friday, May 22, 2009

cache
article list check cache render
list

article new

Friday, May 22, 2009

cache
article list check cache render
list

article new expire cache

Friday, May 22, 2009

cache
article list check cache render
list

article new expire cache

Friday, May 22, 2009

cache
article list check cache render check cache
list

article new expire cache

Friday, May 22, 2009

cache
article list check cache render check cache crashed by non-init @articles
Friday, May 22, 2009

article new
list

expire cache

cache
Solutions?

• defensive: handle the exception • postpone init of @articles • update caches instead of expiring them
none of them is perfect

Friday, May 22, 2009

object id

Friday, May 22, 2009

object id
Check nil? everywhere?

Friday, May 22, 2009

object id
config.whiny_nil = true

Friday, May 22, 2009

validate_uniqueness_of

Friday, May 22, 2009

validate_uniqueness_of
We always get errors like this:
A ActiveRecord::StatementInvalid occurred in fund#add_watch_fund:  Mysql::Error: Duplicate entry '1234-271' for key 2: INSERT INTO `watch_funds` (`account_id`, `position`, `fund_id`, `created_at`) VALUES(1234, 19, 271, '2009-05-06 19:13:50')
Friday, May 22, 2009

validate_uniqueness_of
Process A Process B

Friday, May 22, 2009

validate_uniqueness_of
Process A Process B
unique?

Friday, May 22, 2009

validate_uniqueness_of
Process A Process B
unique?

select ....

Friday, May 22, 2009

validate_uniqueness_of
Process A Process B
unique?

select ....
unique?

Friday, May 22, 2009

validate_uniqueness_of
Process A Process B
unique?

select ....
unique?

Insert

Friday, May 22, 2009

validate_uniqueness_of
Process A Process B
unique?

select ....
unique?

Insert Insert

Friday, May 22, 2009

validate_uniqueness_of
Process A Process B
unique?

select ....
unique?

Insert Insert

crash!

Friday, May 22, 2009

validate_uniqueness_of
validate_uniqueness_of may not guarantee the uniqueness use your own lock if the uniqueness is critical to you.

Friday, May 22, 2009

conditions
Background:

• category has many subcategories • subcategory has many posts • post belongs to subcategory
we need to select all posts in a category.

Friday, May 22, 2009

conditions
What we did:
named_scope :in_category, lambda { |cat| conditions = [cat.subcategories.map {|subcat| 'posts.subcategory_id = ?' }.join(" OR ")] cat.subcategories.each {|subcat| conditions << subcat.id } {:conditions => conditions} }
Friday, May 22, 2009

conditions
Result:
we get all posts when a category has no subcategories

Friday, May 22, 2009

conditions
When category has no subcategory
named_scope :in_category, lambda { |cat| conditions = [cat.subcategories.map {|subcat| 'posts.subcategory_id = ?' }.join(" OR ")] cat.subcategories.each {|subcat| conditions << subcat.id } {:conditions => conditions} }
Friday, May 22, 2009

conditions
When you compose conditions, be aware that sometime nothing to compose means the conditions should match nothing, not the conditions should be empty.

Friday, May 22, 2009

before_create
set a flag if the author of the post is an admin

What we did:

Friday, May 22, 2009

before_create
Result:
Only post by admin can be saved

Friday, May 22, 2009

before_create

All these callbacks are Filters Be careful not to break the filter chain by what you return from the filters!

Friday, May 22, 2009

after_create
send a mail whenever a new record is created

What we did:

Friday, May 22, 2009

after_create
Result:
sometime the record save failed but we still get mail notification

Friday, May 22, 2009

after_create
before_create create after_create all in one transaction begin ... ... commit all the steps between this should be transactional

Friday, May 22, 2009

after_create
What are non-transactional actions?

• send a mail • delete a file • expire a cache

Friday, May 22, 2009

after_create
• try not put non-transaction actions into
transactions.

• after_commit • in controller

Friday, May 22, 2009

Thanks!

Friday, May 22, 2009