On Github lubieniebieski / talk-rules
spoiler alert: this presentation will be about my code-related rules, most of them relatead to stuff I do working day to day with Ruby on Rails
use ? sign
// some_view.haml %h1= book.permissions.for_user(current_user) == 'read'
vs
# book.rb def readable? # some code returning bool value true end # book.readable?
you should be able to read code like a newspaper
if some_value ... if !some_value ... # unless some_value if items
vs
if sume_value.present? ... if some_value.nil? if items.any?
all if statements should operate on boolean (true/false) values
present? nil? any? all? exists? blank? empty?
class Thing # it's formatted badly because it was hard to fit it here
def break!
@broken = true; self
end
def broken?
@broken || false
end
end
thing = Thing.new
#> Thing:0x007fdb6cae0a20
thing.broken?
#> false
thing.break!
#> Thing:0x007fdb6cae0a20 @broken=true
thing.broken?
#> true
downcase, upcase, merge, reject, etc.
class Post has_many :comments end
vs
class Admin::Post validates :title, presence: true end
same object in different contexts has a different meaning
class RaceResult
def initialize(user1, user2)
...
end
end
vs
class RaceResult
def initialize(winner, runnerup)
...
end
end
RaceResult.new(biker_john, biker_sam) RaceResult.new(honda_car, bmw_car)
collection.each do |itm| itm.del! end
vs
cart_items.each do |item| item.buy! end
e.g. container, array, hash - just name it after items it contains
use ff_* for naming features
it's easy to find
- # app/views/users/some/nested/context/_user_info.haml = friend.name
vs
- # app/views/friends/show.haml ... = render 'user_info', user: friend ...
class UpdateUsersAddresses
def up
say "Number of users to update: #{User.count}"
say_with_time 'Updating users addresses...' do
...
if user_update.failed?
say "User ##{user_update.user_id} refused to be updated"
end
...
end
end
end
== 20141009094214 UpdateUsersAddressess: migrating ============= -- Number of users to update: 413 -- Updating users addresses... -- User #666 refused to be updated -> 1.337s
bin/rake db:migrate db:rollback && bin/rake db:migrate
http://robots.thoughtbot.com/workflows-for-writing-migrations-with-rollbacks-in-mind
View Object
class ShoppingCartView
attr_accessor :items
def initialize(items)
self.items = items
end
def total
"#{items.sum(:price)} Rubies"
end
def popular_items
items.select { |i| i.popular? }
end
end
Decorator
class CartItemDecorator < Draper::Decorator
def short_title
object.title.truncate_words(5)
end
def li_css_class
'popular' if popular?
end
end
Template (view)
%h1 Shopping Cart
%ul
- cart.items.each do |item|
%li{ class: item.li_css_class }= item.short_title
= "#{cart.popular_items.count} of your items are very popular"
end
# app/controllers/users_controller.rb
class UsersController
def index
@users = User.all
end
end
and view
%h1 Users: = @users.count
# app/controllers/users_controller.rb
class UsersController
helper_method :users
def index; end
private
def users
User.all
end
end
and view
%h1 Users: = users.count
# app/controllers/users_controller.rb
class UsersController
expose(:users)
# expose(:users) { User.all }
def index; end
end
and view
%h1 Users: = users.count
class ItemRepository
attr_accessor :user
def initialize(user)
self.user = user
end
def all
Item.where(user_id: user.id)
end
def active
all.where(active: true)
end
end
ItemRepository.new(current_user).active
class IdeaSearch
search_on Idea
searches :name, :popular
def search_name
search.where(name: name)
end
def search_popular
search.where('users_count > 10')
end
end
search = IdeaSearch.new(popular: true) search.results
multiple layers = a lot more objects, but it will be much easier to maintain
# ar/post.rb class Post < ActiveRecord::Base belongs_to :author has_many :comments end
# repositories/post_repository.rb
class PostRepository
def all
base_relation.order(author: :id)
end
private
def base_relation
Post.includes(:comments, :author)
end
end
# view_objects/post_view.rb
class PostView
attr_accessor :post
delegate :title, :author, to: :post
def initialize(post)
self.post = post
end
def author_name
author.name
end
def author_avatar
gravatar(author.email)
end
end
# controllers/posts_controller.rb
class PostsController
def index
@posts = decorated_posts
end
private
def decorated_posts
post_repository.all.map { |p| PostView.new(post) }
end
def post_repository
PostRepository.new
end
end
# views/posts/index.haml
%h1 All posts
%ul
- @posts.each do |post|
%li
= post.title
by
= post.author_name
class Users::Create
attr_accessor :email, :user_mailer
def initialize(email:, user_mailer = nil)
self.email = email
self.user_mailer = user_mailer || UserMailer.new
end
def call
user = create_db_user(email)
send_intro_email(user)
user
end
private
def create_db_user(email); end
def send_intro_email(user)
user_mailer.intro(user).deliver
end
end
class Reponse::Success
def success?
true
end
end
class Reponse::Error
def success?
false
end
end
# ... service class def call ... Response::Success.new end
class UsersController
def create
user_creation = Users::Create.new(params[:user]).call
if user_creation.success?
render text: 'OK'
else
render text: 'NOT OK', status: :unprocessable_entity
end
end
end
they're about to change, those are only implementation details
maybe use some dependency injections instead?
remember: buisness is the most important thing (almost all the time)