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

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