Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Robust Rails Apps - VanRuby - March 29, 2017

Robust Rails Apps

Philippe Creux - @pcreux

#vanruby - March 29th, 2017


⭐️ Robust Rails Apps ⭐️

😸 Philippe Creux 🐦 @pcreux

💎 vanruby 📆 March 29th, 2017

Silent Failures

    🙈
# app/controllers/users_controller.rb
def show
@user = User.find_by_uuid(params[:uuid])
end
# app/views/users/show.html.erb
<%= @user.name %>
# app/controllers/users_controller.rb
def show
@user = User.find_by_uuid(params[:uuid])
end
# app/views/users/show.html.erb
<%= @user.name %> 💥
# NoMethodError: undefined method `name` for nil
# app/controllers/users_controller.rb
def show
@user = User.find_by_uuid!(params[:uuid])
end
# app/views/users/show.html.erb
<%= @user.name %>
# app/controllers/users_controller.rb
def show
@user = User.find_by_uuid!(params[:uuid]) 💥
end
# app/views/users/show.html.erb
<%= @user.name %>
# ActiveRecord::NotFoundError: Can't find user with uuid "foo"
require 'open-uri'
require 'json'
json = open("ftp://webftp.vancouver.ca/OpenData/json/LostAnimals.json").read
lost_animals = JSON.parse(json)
p lost_animals.find { |animal| animal["name"] == "Koko" }
require 'open-uri'
require 'json'
json = open("ftp://webftp.vancouver.ca/OpenData/json/LostAnimals.json").read
lost_animals = JSON.parse(json)
p lost_animals.find { |animal| animal["name"] == "Koko" }
# => nil 😿
require 'open-uri'
require 'json'
json = open("ftp://webftp.vancouver.ca/OpenData/json/LostAnimals.json").read
lost_animals = JSON.parse(json)
p lost_animals.find { |animal| animal.fetch("name") == "Koko" }
# in `fetch': key not found: "name" (KeyError)
# 🙀
require 'open-uri'
require 'json'
json = open("ftp://webftp.vancouver.ca/OpenData/json/LostAnimals.json").read
lost_animals = JSON.parse(json)
p lost_animals.find { |animal| animal.fetch("Name") == "Koko" }
# => {"json_featuretype"=>"LostAnimals", "Date"=>"2017-02-06", "Color"=>"Grey/Blk/with White chest", "Breed"=>"Cat - DSH", "Sex"=>"M", "State"=>"Lost", "Name"=>"Koko", "DateCreated"=>"2017-02-06"}

Silent Failures

    🙉
def publish_post(post, timeframe: :now)
case timeframe
when :now
post.publish!
when :tomorrow
post.delay(run_at: 1.day.from_now).publish!
end
end
def publish_post(post, timeframe: :now)
case timeframe
when :now
post.publish!
when :tomorrow
post.delay(run_at: 1.day.from_now).publish!
end
end
# posts_controller.rb
def publish
post = Post.find(params[:id])
publish_post(post, params[:timeframe])
redirect_to post, notice: "Done! 🎉 "
end
def publish_post(post, timeframe: :now)
case timeframe
when :now
post.publish!
when :tomorrow
post.delay(run_at: 1.day.from_now).publish!
else
raise ArgumentError, "Invalid timeframe: #{timeframe.inspect}"
end
end
# posts_controller.rb
def publish
post = Post.find(params[:id])
publish_post(post, params[:timeframe])
redirect_to post, notice: "Done! 🎉 "
end
# => ArgumentError: Invalid timeframe: "now"
def pay(invoice)
billing_interface.charge!(invoice.amount)
invoice.update_attributes(
paid_at: Time.now
)
end
def pay(invoice)
billing_interface.charge!(invoice.amount)
invoice.update_attributes(
paid_at: Time.now
)
end
# ---
pay(invoice)
invoice.paid_at
# => nil
🤑
def pay(invoice)
billing_interface.charge!(invoice.amount)
invoice.update_attributes(
paid_at: Time.now
)
end
# ---
pay(invoice)
invoice.paid_at
# => nil
🤑
# ---
invoice.valid?
# => false
invoice.errors
# => [ { address: "is invalid" } ]
😰
def pay(invoice)
billing_interface.charge!(invoice.amount)
invoice.update_attributes!(
paid_at: Time.now
)
end
def pay(invoice)
billing_interface.charge!(product.amount)
invoice.update_attributes!(
paid_at: Time.now
)
end
# ---
pay(invoice)
# => ActiveRecord::InvalidRecord ... address in invalid...
😎
def pay(invoice)
billing_interface.charge!(product.amount) 🤡
invoice.update_attributes!(
paid_at: Time.now
)
end
# ---
pay(invoice)
# => ActiveRecord::InvalidRecord ... address in invalid...
🤡
def pay(invoice)
ActiveRecord::Base.transaction do
invoice.update_attributes!(
paid_at: Time.now
)
billing_interface.charge!(invoice.amount)
end
end
# ---
pay(invoice)
# => ActiveRecord::InvalidRecord ... address in invalid...
🤓

Database is your friend™

        📖
def limit(feature)
feature.plan_limit + feature.bonuses + feature.add_ons
end
def limit(feature)
feature.plan_limit + feature.bonuses + feature.add_ons
end
# => TypeError: nil can't be coerced into Fixnum
def limit(feature)
feature.plan_limit + feature.bonuses + feature.add_ons
end
# => TypeError: nil can't be coerced into Fixnum
feature
# => <Feature plan_limit=2 bonuses=nil add_ons=nil>
def limit(feature)
(feature.plan_limit || 0) + (feature.bonuses || 0) + (feature.add_ons || 0)
end
# => 2
🎉
def limit(feature)
feature.plan_limit + feature.bonuses + feature.add_ons
end
# => TypeError: nil can't be coerced into Fixnum
feature
# => <Feature plan_limit=2 bonuses=nil add_ons=nil>
create_table :features do |t|
t.string "code"
t.integer "plan_limit"
t.integer "bonuses"
t.integer "add_ons"
end
def limit(feature)
feature.plan_limit + feature.bonuses + feature.add_ons
end
# => TypeError: nil can't be coerced into Fixnum
feature
# => <Feature plan_limit=2 bonuses=nil add_ons=nil>
create_table :features do |t|
t.string "code", null: false
t.integer "plan_limit", null: false, default: 0
t.integer "bonuses", null: false, default: 0
t.integer "add_ons", null: false, default: 0
end
def limit(feature)
feature.plan_limit + feature.bonuses + feature.add_ons
end
# => 2
feature
# => <Feature plan_limit=2 bonuses=0 add_ons=0>
create_table :features do |t|
t.string "code", null: false
t.integer "plan_limit", null: false, default: 0
t.integer "bonuses", null: false, default: 0
t.integer "add_ons", null: false, default: 0
end
class Account < ActiveRecord::Base
has_one :account_details
end
class AccountDetails < ActiveRecord::Base
belongs_to :account
end
class Account < ActiveRecord::Base
has_one :account_details
end
class AccountDetails < ActiveRecord::Base
belongs_to :account
end
account = Account.create!
# => <Account id=1>
class Account < ActiveRecord::Base
has_one :account_details
end
class AccountDetails < ActiveRecord::Base
belongs_to :account
end
account = Account.create!
# => <Account id=1>
account.account_details
# => nil
account.create_account_details!
# => <AccountDetails account_id=1>
class Account < ActiveRecord::Base
has_one :account_details
end
class AccountDetails < ActiveRecord::Base
belongs_to :account
end
account = Account.create!
# => <Account id=1>
account.account_details
# => nil
account.create_account_details!
# => <AccountDetails account_id=1>
account.create_account_details!
# => <AccountDetails account_id=2>
class Account < ActiveRecord::Base
has_one :account_details
end
class AccountDetails < ActiveRecord::Base
belongs_to :account
end
account = Account.create!
# => <Account id=1>
account.account_details
# => nil
account.create_account_details!
# => <AccountDetails account_id=1>
account.create_account_details!
# => <AccountDetails account_id=2>
account.account_details
# => <AccountDetails account_id=1>
account.account_details
# => <AccountDetails account_id=2>
class Account < ActiveRecord::Base
has_one :account_details
end
class AccountDetails < ActiveRecord::Base
belongs_to :account
end
# ---
add_index :account_details, :account_id, unique: true
class Account < ActiveRecord::Base
has_one :account_details
end
class AccountDetails < ActiveRecord::Base
belongs_to :account
end
# ---
add_index :account_details, :account_id, unique: true
account = Account.create!
# => <Account id=1>
account.account_details
# => nil
account.create_account_details!
# => <AccountDetails account_id=1>
class Account < ActiveRecord::Base
has_one :account_details
end
class AccountDetails < ActiveRecord::Base
belongs_to :account
end
# ---
add_index :account_details, :account_id, unique: true
account = Account.create!
# => <Account id=1>
account.account_details
# => nil
account.create_account_details!
# => <AccountDetails account_id=1>
account.create_account_details!
# => <ActiveRecord::UniqueIndexError ....>
class Account < ActiveRecord::Base
has_one :subscription
end
class Subscription < ActiveRecord::Base
belongs_to :account
acts_as_paranoid
end
# ---
create_table "subscriptions" do
t.string "plan_code", null: false
t.integer "account_id", null: false
t.timestamp "deleted_at"
end
add_index "subscriptions", [:account_id, :deleted_at], unique: true
add_foreign_key "subscriptions", "accounts"
user = User.find_by_uuid(params[:uuid])
name = hash['name']
case action
when :upgrade then # ...
when :downgrade then # ...
end
user.save
create_table "subscriptions" do
t.string "plan_code"
t.integer "account_id"
t.timestamp "deleted_at"
end
user = User.find_by_uuid!(params[:uuid])
name = hash.fetch('name')
case action
when :upgrade then # ...
when :downgrade then # ...
else raise ArgumentError
end
user.save!
create_table "subscriptions" do
t.string "plan_code", null: false
t.integer "account_id", null: false
t.timestamp "deleted_at"
end
add_index "subscriptions", [:account_id, :deleted_at], unique: true
add_foreign_key "subscriptions", "accounts"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment