Pragmatic Patterns of Ruby on Rails Yasuko OHBA( )
what I do • developer of Rails applications • • Everyleaf Corporation
sponsor booth • first time to be a sponsor • we serve candies • you can play ‘Romantic Ruby’ !
my products • home accounting web service http://www.kozuchi.net • http://github.com/everyleaf/kozuchi/ tree
personal message
I started programming when I was 14 years old
school for girls
I was alone
got the job
find similar people
however
‘enterprise’
only activities inside the company
other things mount up
I was lost
want to write codes
that’s when..
Ruby was given
life becomes happier
Ruby for enterprises
the way to live happily writing codes
And...
great communities
many friends
thank you !!
today’s presentation
coding patterns with Ruby on Rails
the target • large & complicated applications • team
problems of large applications • different coding styles • nasty parts made
becomes harder to maintenance
keep your codes nice
what is the ‘nice code’? • good design • easy to understand • easy to find something wrong
coding patterns work fine • make and share coding patterns • add some DSL (not too much)
efficient, of course
what I won’t say • how to get the best performance • ActiveRecord’s alternatives
ActiveRecord
frankly speaking,
I love ActiveRecord
because
OOP
still able to maintenance when it gets complicated
pragmatic
the heart of RoR
many AR features in this presentation
examples
show permitted data
find starts from an AR object
find a note by id /note/3 def show @note = Note.find(params[:id]) end
find a note of the current user /note/3 @note = Note.find_by_id_and_user_id( params[:id], current_user.id) raise ActiveRecord::RecordNotFound unless @note
/note/3 @note = Note.written_by( curret_user.id).find(params[:id])
hard to notice the luck of condition! @note = Note.find(params[:id]) @note = Note.find_by_id_and_user_id( params[:id], current_user.id) @note = Note.written_by( current_user).find(params[:id])
so,
find starts from an AR object
starts from an object def show @note = current_user.notes.find( params[:id]) end
use the association def show @note = current_user.notes.find( params[:id]) end
association class User < ActiveRecord::Base has_many :notes end
easy to notice problems @note = current_user.notes.find( params[:id]) @note = Note.find(params[:id])
easy to see who can access
who can access @note = current_user.notes.find( params[:id])
filters to find the starting AR object
with the previous pattern
most actions will use the AR object
example; CRUD of the specified group’s notes
• URL • /groups/15/notes • /groups/15/notes/3
• Note has group_id class Note < ActiveRecord::Base belongs_to :group end class Group < ActiveRecord::Base has_many :notes end
def index @group = Group.find(params[:group_id]) @notes = @group.notes.paginate(:page => params[:page]) end def show @group = Group.find(params[:group_id]) @note = @group.notes.find(params[:id]) end
the duplicated line @group = Group.find(params[:group_id])
write that line in a filter
what’s the filter? • the separated logic which would be called around action • declarative
find the group in a filter class GroupNotesController < ApplicationController before_filter :find_group .......actions ...... private def find_group @group = Group.find(params[:group_id]) end end
DRY before_filter :find_group def index @notes = @group.notes.paginate(:page => params[:page]) end def show @note = @group.notes.find(params[:id]) end
easy to change access control
example; change to allow access to members only def find_group @group = current_user.groups.find( params[:group_id] end
other merits
secure
safe if it starts from @group def index @notes = @group.notes.paginate(:page => params[:page]) end def show @note = @group.notes.find(params[:id]) end
filter urges developers to use @group
and
readable
you can understand the controller’s summary from name and filters
understand the summary quickly class GroupNotesController < ApplicationController before_filter :find_group
the points • a good controller name • readable filters
complicated business logics
want to write business logics in models
because
merits of writing business logics in models • easy to test • easy to reuse • readable, easy to find the target codes
however
‘how’ matters a lot
a bad example class MyModel < ActiveRecord::Base def do_create(params) .... def do_destroy(params) ... end
why is it bad ? • it breaks MVC • hard to reuse
now
let’s move codes from controller to model in good way
move the logic branching on parameters from C to M
branching on parameters def update @note.attributes = params[:note] @tags = params[:auto_tagging] == '1' ? generate_tags(@note) : [] # do saving and tagging ... end
often written in controller
to move to model
add an attribute for branching to the model
the condition params[:auto_tagging] == '1'
as an attribute of the model class Note < ActiveRecord::Base attr_accessor :auto_tagging end
change parameters structure { :note => {.....}, :auto_tagging => '1' } { :note => { ....., :auto_tagging => '1' }}
change the view for it <%= check_box_tag :auto_tagging %> <% form_for :note, ... do |f |%> <%= f.check_box :auto_tagging %> <% end %>
now it’s in params[:note] def update @note.attributes = params[:note] if params[:auto_tagging] == '1' generate_tags(@note) end .....
can branch in the model class Note < ActiveRecord::Base .... if auto_tagging ... end end
finished to move branching
move generate_tags next
move the logic processing other models from C to M
the logic processing other models def update generate_tags(@note) end private def generate_tags(note) tags = Tag.extract(note.body) note.tag_list = tags.join(',') end
also often written in controller
put it into model’s callback class Note < ActiveRecord::Base before_save :generate_taggings private def generate_taggings return unless auto_tagging tags = Tag.extract(body) self.tag_list = tags.join(',') end end
what’s the model’s callback? • methods called before/after save or destroy
Rails Model You normal method call save (validation) before_save do saving after_save
self-directive models
now we’ve done it ! • easy to test • easy to reuse • readable, easy to find the target codes
other patterns • multi levels for validation • design & coding policy for STI • use owner object’s attributes in association • how to make routes.rb a little readable
the last topic
how to find coding patterns
in my case
try to choose the most natural style for RoR
what is the most natural way in Ruby on Rails?
1. OOP
express business logics as models
always think who should do that job
Note or User ? @note = Note.find_by_id_and_user_id( params[:id], current_user.id) @note = current_user.notes.find( params[:id])
you can’t always decide it from tables
decide it in objects world
2. follow the principles of RoR
principles of RoR • DRY • CoC • RESTful
accept RESTful in Ruby on Rails
how to design controllers ?
one controller for a type of resources
what’s the resources?
you can find it in URL /companies/everyleaf/notes/3
design of controllers
starts from design of URLs
roughly speaking,
Model Class 1 1..* Resource
the point is...
one controller should provide one resource type’s CRUD
might have been derailed class BlogsController < ApplicationController def create_comment end def comments end def destroy_comment ...
one more
write codes in model’s standard flows
standard flows • find • new → save (create) • find → save (update) • find → destroy
inside of a flow
new attribute = before_ build validation before_ after_ validation save validation creation after_save
write codes in appropriate place
where, what new attribute = before_ build validation before_ after_ validation save validation creation after_save
try to choose the most natural style for RoR
don’t go too far from the basic styles
because
it’s normal to Rails programmers
easy to read for everyone
for that purpose
use Ruby’s flexibility
summary
in developing large & complicated application
it’s important to keep codes nice
for that purpose
be aware of coding patterns, and share them
coding patterns fitting Ruby on Rails
make source codes easy to read for general developers
it means easy to maintain
thank you !

Pragmatic Patterns of Ruby on Rails - Ruby Kaigi2009