Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Perusing the Rails Source Code

519 views

Published on

View this talk as a blog: http://alexkitchens.net/2017/05/06/rails-source-code.html

Open source projects like Rails are intimidating, especially as a beginner. It’s hard to look at the code and know what it does. But Ruby on Rails is more than just code. Written into it are years of research, discussions, and motivations. Also written into it are bugs, typos, and all of the pieces that make the code human. This talk outlines steps you can take to explore the inner workings of Rails and gain context on its design. Understanding how Rails works will allow you to write better Rails applications and better Ruby code. You will leave with many resources and tips on perusing Rails.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Perusing the Rails Source Code

  1. 1. @alexcameron89 PERUSING THE RAILS SOURCE CODE A BEGINNERS GUIDE
  2. 2. ⋆ Star 30,000
  3. 3. Time Internals Comprehension Me
  4. 4. Time Internals Comprehension You
  5. 5. CAUTION Side Effects Perusing Rails
  6. 6. You might experience an increase in Ruby knowledge
  7. 7. You might experience an increase in Git knowledge
  8. 8. You might experience enlightenment to magic
  9. 9. Rails GitHub Repository Rails Dependencies Setup guides.rubyonrails.org/development_dependencies_install github.com/rails/rails
  10. 10. actioncable actionmailer actionpack activejob guides Gemfile Gemfile.lock rails.gemspec actionview activerecord activesupport activemodel railties MIT-LICENSE CONTRIBUTING.md README.md ~ ❯ cd rails/ ~/rails ❯ ls
  11. 11. actioncable actionmailer actionpack activejob guides Gemfile Gemfile.lock rails.gemspec actionview activerecord activesupport activemodel railties MIT-LICENSE CONTRIBUTING.md README.md ~ ❯ cd rails/ ~/rails ❯ ls
  12. 12. ~ ❯ rails server
  13. 13. Request ~ ❯ rails server
  14. 14. Routes Request ~ ❯ rails server
  15. 15. Controller Routes Request ~ ❯ rails server
  16. 16. Controller Routes Request ~ ❯ rails server Model
  17. 17. Controller Routes Request ~ ❯ rails server View Model
  18. 18. Controller Routes Request ~ ❯ rails server View Model
  19. 19. Controller Routes Request Response ~ ❯ rails server View Model
  20. 20. Controller Routes ActionPack Request Response ~ ❯ rails server View Model
  21. 21. Controller ActiveRecord ActiveModel Routes ActionPack Request Response ~ ❯ rails server View Model
  22. 22. Controller ActiveRecord ActiveModel Routes ActionPack ActionView Request Response ~ ❯ rails server View Model
  23. 23. Controller ActiveRecord ActiveModel Routes ActionPack ActionView Request Response ~ ❯ rails server View Model Railties
  24. 24. Controller ActiveRecord ActiveModel Routes ⋆ ⋆ ⋆ ⋆ ActiveSupport ActionPack ActionView Request Response ~ ❯ rails server View Model Railties
  25. 25. ActionMailer Mail Delivery ActiveJob Jobs/Tasks ActionCable Integrated WebSockets
  26. 26. + Your Story tenderlove github ActionMailer Mail Delivery ActiveJob Jobs/Tasks ActionCable Integrated WebSockets
  27. 27. + Your Story tenderlove github ActionMailer Mail Delivery ActiveJob Jobs/Tasks ActionCable Integrated WebSockets tenderlove Aaron Patterson What room are you in? South Ballroom
  28. 28. actioncable actionmailer actionpack activejob guides Gemfile Gemfile.lock rails.gemspec ~/rails ❯ ls
  29. 29. lib test README.rdoc activerecord.gemspec CHANGELOG.md bin examples MIT-LICENSE RUNNING_UNIT_TESTS.rdoc ~/rails ❯ cd activerecord/ ~/rails/activerecord ❯ ls
  30. 30. s.add_dependency "activesupport", version s.add_dependency "activemodel", version s.add_dependency "arel", "~> 8.0" activerecord/active_record.gemspec
  31. 31. ~/rails/activerecord ❯ cd lib ~/rails/activerecord/lib ❯ ls active_record rails active_record.rb
  32. 32. attributes.rb attribute_mutation_tracker.rb base.rb persistence.rb reflection.rb relation.rb schema.rb . . . ~/rails/activerecord/lib ❯ cd active_record ~/rails/activerecord/lib/active_record ❯ ls
  33. 33. ~/rails/activerecord/lib ❯ cd active_record ~/rails/activerecord/lib/active_record ❯ ls attributes.rb attribute_mutation_tracker.rb base.rb persistence.rb reflection.rb relation.rb schema.rb . . .
  34. 34. ~/rails/activerecord/lib ❯ cd active_record ~/rails/activerecord/lib/active_record ❯ ls attributes.rb attribute_mutation_tracker.rb base.rb persistence.rb reflection.rb relation.rb schema.rb . . .
  35. 35. Post.find_by # => #<Post id:1, title: "Example", ...> Post.where # => #<ActiveRecord::Relation [#<Post ...>]>
  36. 36. activerecord/lib/active_record/relation/finder_methods.rb 77 def find_by(arg, *args) 78 where(arg, *args).take 79 rescue ::RangeError 80 nil 81 end
  37. 37. Ǹ Digging Through Code With Ruby
  38. 38. Post.method(:create).source_location ~/blog ❯ rails console
  39. 39. Post.method(:create).source_location => [“…/lib/active_record/persistence.rb”, 29] ~/blog ❯ rails console
  40. 40. activerecord/lib/active_record/persistence.rb 29 def create(attributes = nil, &block) . . . 33 object = new(. . .) 34 object.save 35 object 36 . . . 37 end
  41. 41. Post.new.method(:save).source_location => [“…/lib/active_record/suppressor.rb”, 41]
  42. 42. Persistence
  43. 43. 41: def save(*) # :nodoc: => 42: SuppressorRegistry.suppressed[self.class.name] ? true : super 43: end activerecord/lib/active_record/suppressor.rb
  44. 44. 306: def save(*) #:nodoc: 307: rollback_active_record_state! do => 308: with_transaction_returning_status { super } 309: end 310: end activerecord/lib/active_record/transactions.rb
  45. 45. 34: def save(*) => 35: if status = super 36: changes_applied 37: end 38: status 39: end activerecord/lib/active_record/attribute_methods/dirty.rb
  46. 46. 43: def save(options = {}) => 44: perform_validations(options) ? super : false 45: end activerecord/lib/active_record/validations.rb
  47. 47. 124: def save(*args) => 125: create_or_update(*args) 126: rescue ActiveRecord::RecordInvalid 127: false 128: end activerecord/lib/active_record/persistence.rb
  48. 48. post = Post.new post.method(:save).source_location => [“. . . /suppressor.rb”] post.method(:save).super_method.source_location => [“. . . /transactions.rb”]
  49. 49. ... require "byebug" ... byebug Post.create activerecord/active_record.gemspec
  50. 50. ~/rails ❯ ls rails/guides/bug_report_templates/ action_controller_master.rb active_job_master.rb active_record_master.rb active_record_migrations_master.rb benchmark.rb generic_master.rb
  51. 51. require "bundler/inline" gemfile(true) do source "https://rubygems.org" gem "rails", github: "rails/rails" gem "arel", github: "rails/arel" gem "sqlite3" # or "pg" or "mysql2" . . . end require "active_record" require "minitest/autorun" require "logger"
  52. 52. require "bundler/inline" gemfile(true) do source "https://rubygems.org" gem "rails", path: “path/to/your/rails“ gem "arel", github: "rails/arel" gem "sqlite3" # or "pg" or "mysql2" . . . end require "active_record" require "minitest/autorun" require "logger"
  53. 53. ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Schema.define do create_table :posts do |t| t.string :title end end class Post < ActiveRecord::Base end . . .
  54. 54. . . . class BugTest < Minitest::Test def test_association_stuff post = Post.create(title: “Hello”) assert_equal “Hello”, post.title end end
  55. 55. . . . class BugTest < Minitest::Test def test_association_stuff post = Post.create(title: “Hello”) assert_equal “Hello”, post.title end end
  56. 56. . . . post = Post.create(title: “Hello”)
  57. 57. require “pry” . . . post = Post.create(title: “Hello”) binding.pry [1] pry(main)>
  58. 58. require “pry” . . . post = Post.create(title: “Hello”) binding.pry [1] pry(main)> Post.all => [#<Post id: 1, title: "Hello">]
  59. 59. require “byebug” . . . byebug Post.create(title: "Fancy Post")
  60. 60. [31, 33] in active_record_master.rb 31: 32: byebug => 33: Post.create (byebug) s
  61. 61. in activerecord/lib/active_record/persistence.rb 29: def create(attributes = nil, &block) => 30: if attributes.is_a?(Array) . . . 32: else 33: object = new(attributes, &block) (byebug) n
  62. 62. in activerecord/lib/active_record/persistence.rb 29: def create(attributes = nil, &block) 30: if attributes.is_a?(Array) . . . 32: else => 33: object = new(attributes, &block) (byebug) var local
  63. 63. in activerecord/lib/active_record/persistence.rb 29: def create(attributes = nil, &block) 30: if attributes.is_a?(Array) . . . 32: else => 33: object = new(attributes, &block) (byebug) var local attributes = {:title=>"Hello"} block = nil
  64. 64. It can be troubling to know simultaneously the exact value and correct execution of a variable. - Werner Heisenbug
  65. 65. I know this I don’t know this Association Reflection Attributes Attribute Mutation Tracker Relation . . .
  66. 66. I know this I don’t know this Association Reflection Attributes Attribute Mutation Tracker Relation . . .
  67. 67. Delete It
  68. 68. Delete It
  69. 69. def define_method_attribute=(name) safe_name = name.unpack("h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) . . . end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end activerecord/lib/active_record/attribute_methods/write.rb Creates post.title=
  70. 70. def define_method_attribute=(name) safe_name = name.unpack(“h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) . . . end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end activerecord/lib/active_record/attribute_methods/write.rb => “479647c656”
  71. 71. def define_method_attribute=(name) safe_name = name.unpack(“h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) . . . end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end activerecord/lib/active_record/attribute_methods/write.rb
  72. 72. def define_method_attribute=(name) safe_name = name.unpack(“h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) . . . end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end activerecord/lib/active_record/attribute_methods/write.rb => “__temp__479647c656”
  73. 73. def define_method_attribute=(name) safe_name = name.unpack(“h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) . . . end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end activerecord/lib/active_record/attribute_methods/write.rb
  74. 74. def define_method_attribute=(name) safe_name = name.unpack(“h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) . . . end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end activerecord/lib/active_record/attribute_methods/write.rb
  75. 75. def define_method_attribute=(name) safe_name = name.unpack(“h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) . . . end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end activerecord/lib/active_record/attribute_methods/write.rb
  76. 76. def define_method_attribute=(name) safe_name = name.unpack(“h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def #{name}=(value) . . . end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end activerecord/lib/active_record/attribute_methods/write.rb
  77. 77. def define_method_attribute=(name) safe_name = name.unpack(“h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def #{name}=(value) . . . end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= STR end activerecord/lib/active_record/attribute_methods/write.rb
  78. 78. def define_method_attribute=(name) safe_name = name.unpack(“h*".freeze).first . . . generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def #{name}=(value) . . . end STR end activerecord/lib/active_record/attribute_methods/write.rb
  79. 79. 1) Error: BasicsTest#test_non_valid_identifier_column_name: SyntaxError: formal argument cannot be a global variable def a$b=(value) def test_non_valid_identifier_column_name weird = Weird.create("a$b" => "value") weird.reload assert_equal "value", weird.send("a$b") assert_equal "value", weird.read_attribute("a$b") . . . end activerecord/lib/active_record/attribute_methods/write.rb
  80. 80. ƶ Finding Information With Git
  81. 81. ~/rails ❯ git blame . . . 7c70430c Ryuta Kamizono 2016-08-23 def __temp__#{safe_name}=(value) 7c70430c Ryuta Kamizono 2016-08-23 name = . . . 7c70430c Ryuta Kamizono 2016-08-23 write_attribute(name, value) 7c70430c Ryuta Kamizono 2016-08-23 end . . .
  82. 82. --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -15,13 +15,13 @@ def define_method_attribute=(name) ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name}=(value) - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - write_attribute(name, value) - end - alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= - undef_method :__temp__#{safe_name}= - STR + def __temp__#{safe_name}=(value) + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + write_attribute(name, value) + end + alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= + undef_method :__temp__#{safe_name}= + STR end end
  83. 83. ~/rails ❯ git blame -w . . . b785e921 Aaron Patterson 2013-07-03 def __temp__#{safe_name}=(value) b785e921 Aaron Patterson 2013-07-03 name = . . . b785e921 Aaron Patterson 2013-07-03 write_attribute(name, value) b785e921 Aaron Patterson 2013-07-03 end . . .
  84. 84. ~/rails ❯ git log -S "alias_method #{(name + '=').inspect}" commit b785e921d186753d905c1d0415b91d0987958028 Author: Aaron Patterson <lovingtenderly@tenderlove.com> Date: Wed Jul 3 14:18:31 2013 -0700 method transplanting between modules isn't supported on 1.9 commit 55ac7db11bd2fc0cf06d7184f013018fa4be0e9a Author: Aaron Patterson <lovingtenderly@tenderlove.com> Date: Wed Jul 3 10:56:56 2013 -0700 keep a cache of writer methods . . .
  85. 85. ~/rails ❯ git log --patch --pickaxe-all -S "alias_method #{(name + '=').inspect}" commit b785e921d186753d905c1d0415b91d0987958028 Author: Aaron Patterson <lovingtenderly@tenderlove.com> Date: Wed Jul 3 14:18:31 2013 -0700 method transplanting between modules isn't supported on 1.9 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/method_transplanting' + module ActiveRecord module AttributeMethods @@ -62,9 +64,30 @@ def cache_attribute?(attr_name)
  86. 86. ~/rails ❯ git log --reverse --patch --pickaxe-all -S "alias_method #{(name + '=').inspect}" commit f1765019ce9b6292f2264b4601dad5daaffe3a89 Author: Jon Leighton <j@jonathanleighton.com> Date: Fri Oct 12 12:33:11 2012 +0100 Don't allocate new strings in compiled attribute methods --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -9,15 +9,19 @@ module Write module ClassMethods protected - def define_method_attribute=(attr_name) - if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
  87. 87. ❯ Initial Commit commit baa237c974fee8023dd704a4efb418ff0e963de0 Author: Santiago Pastorino <santiago@wyeworks.com> Date: Mon Mar 21 21:36:05 2011 -0300 Allow to read and write AR attributes with non valid identifiers
  88. 88. Vim Fugitive GitHub
  89. 89. Learning More with GitHub
  90. 90. DEPRECATION WARNING: `redirect_to :back` is deprecated and will be removed from Rails 5.1. Please use `redirect_back(fallback_location: fallback_location)` . . .
  91. 91. ~/rails ❯ git log -S "Please use `redirect_back(fallback_loc"
  92. 92. ~/rails ❯ git log -S "Please use `redirect_back(fallback_loc" commit 13fd5586cef628a71e0e2900820010742a911099 Author: Derek Prior Date: Tue Dec 15 20:17:32 2015 -0500 Add `redirect_back` for safer referrer redirects . . . When there is no referrer available on the request, `redirect_to :back` will raise `ActionController::RedirectBackError`, usually resulting in an application error. . . .
  93. 93. General Rails Knowledge
  94. 94. Reproduction Script Issue: #51367 General Rails Knowledge
  95. 95. Reproduction Script Issue: #51367 General Rails Knowledge
  96. 96. Pull Request Received
  97. 97. Discussion between Contributors & Maintainers
  98. 98. More Commits Added
  99. 99. Rebased, Merged into master
  100. 100. ƶ
  101. 101. Takeaways from PRs & Issues
  102. 102. With reproduction steps Attached PR
  103. 103. Resources
  104. 104. the_act_of_making_love_tenderly.rb I am a puts debuggerer tenderlovemaking.com Aaron Patterson
  105. 105. you_should_tell.rb eileencodes.com/speaking/ Breaking Down the Barrier: Demystifying Contributing to Rails System Tests PR github.com/rails/rails/pull/26703 Eileen Uchitelle
  106. 106. hi_sean_hi_derek.rb The Bike Shed @_bikeshed Derek Prior Sean Griffin Amanda Hill
  107. 107. magical_learning_elixir.rb Crafting Rails 4 Applications pragprog.com José Valim
  108. 108. fxn.rb The Rails Boot Process https://youtu.be/vSza2FZucV8 Xavier Noria
  109. 109. Thank You @alexcameron89 Slides: LINK HERE

×