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)