Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rails talk - RubyLightningTalksTO

Rails talk - RubyLightningTalksTO

Common Rails mistakes, and things you should probably avoid

Avatar for Arthur Nogueira Neves

Arthur Nogueira Neves

June 10, 2016
Tweet

More Decks by Arthur Nogueira Neves

Other Decks in Technology

Transcript

  1. ▸ I work at GitHub ▸ ❤ Open source ▸

    rubygems.org maintainer ▸ Part of the Rails committer team ▸ GPG fingerprint: 1E433E17
  2. MODEL CALLBACKS : KEY POINTS ▸ Most of them run

    inside a transaction. ▸ The returned value matters. ▸ `before_destroy { return false }` ▸ Rails uses and sets some default callbacks. ▸ `has_many :assets, dependent: :destroy` CALLBACKS
  3. COMMON MISTAKES: ▸ Postpone things to run on jobs. ▸

    Data integrity issues. ▸ I/O calls in callbacks. ▸ Performance issues. CALLBACKS
  4. CALLBACKS 1 class User < ActiveRecord::Base 2 after_save :update_friends_count 3

    def update_friends_count 4 FriendsCounterJob.perform_later(self) 5 end 6 7 after_save :upload_avatar 8 def upload_avatar 9 S3.upload(avatar_url) 10 end 11 end
  5. CALLBACKS FIX: ▸ Don’t queue data that needs to be

    consistent. ▸ callbacks that run after the transaction. ▸ after_commit ▸ after_rollback
  6. KEY POINTS ▸ They don’t run inside the transaction. ▸

    They run after the transaction. AFTER_COMMIT & AFTER_ROLLBACK
  7. CATCHES ▸ They don’t stop the transaction if there is

    any error ▸ The error doesn’t even get raised. AFTER_COMMIT & AFTER_ROLLBACK
  8. AFTER_COMMIT 1 class S3; end 2 def S3.upload(*) 3 raise

    "IO error" 4 end 5 6 class User < ActiveRecord::Base 7 after_commit :upload_avatar 8 def upload_avatar 9 S3.upload(file) 10 end 11 end 12 13 class BugTest < ActiveSupport::TestCase 14 def test_upload_the_avatar_if_io_error 15 assert_raises { User.create } 16 end 17 end
  9. AFTER_COMMIT 1 class S3; end 2 def S3.upload(*) 3 raise

    "IO error" 4 end 5 6 class User < ActiveRecord::Base 7 after_commit :upload_avatar 8 def upload_avatar 9 S3.upload(file) 10 end 11 end 12 13 class BugTest < ActiveSupport::TestCase 14 def test_upload_the_avatar_if_io_error 15 assert_raises { User.create } 16 end 17 end
  10. STI: SINGLE TABLE INHERITANCE 1 class User < ActiveRecord::Base 2

    def authenticate(password); end 3 def upload_avatar(file); end 4 def update_code(); end 5 end 6 7 class Organization < User 8 end 9 10 User.create(login: 'arthurnn') 11 Organization.create(login: 'rails')
  11. STI ▸ Everything lives in the same table ▸ Hard

    to scale. ▸ Hard to move away from it. ▸ You don’t need STI for code reuse. PROBLEMS:
  12. STI: SINGLE TABLE INHERITANCE DATABASE INDEX PERFORMANCE ▸ Database index

    search is: O(log N) ▸ Database non-index search: O(N) ▸ `type` column needs to be on most of indexes.
  13. 1 module Authenticable 2 def authenticate(password); end 3 end 4

    module Avatarable 5 def upload_avatar(file); end 6 end 7 8 class User < ActiveRecord::Base 9 include Authenticable 10 include Avatarable 11 end 12 13 class Organization < ActiveRecord::Base 14 include Authenticable 15 include Avatarable 16 end 17 18 User.create(login: 'arthurnn') 19 Organization.create(login: 'rails') STI: SOLUTION
  14. DEFAULT SCOPE 1 class Shop < ActiveRecord::Base 2 default_scope {

    where(deleted: false) } 3 4 def destroy 5 _run_destroy_callbacks do 6 update(deleted: true) 7 end 8 end 9 end 10 11 shop = Shop.find(42) 12 shop.destroy 13 14 Shop.find(42) 15 => nil
  15. DEFAULT SCOPE PROBLEMS: ▸ default_scope is too magical. ▸ There

    is some bugs in rails, still today, on default_scope ▸ Same indexes issues. All the queries need to include scope on the index.
  16. DEFAULT SCOPE : SOLUTION 1 class Shop < ActiveRecord::Base 2

    before_destroy :archive_record 3 def archive_record 4 ShopArchive.archive_shop(self) 5 end 6 end 7 8 shop = Shop.find(42) 9 shop.destroy 10 11 Shop.find(42) 12 => nil
  17. CONCLUSION ▸ Be aware of callbacks magic, and try to

    understand where and when they run. ▸ Don’t use STI ▸ Don’t use default_scope