Anatomy of constant lookup and autoloading in Rails

1b0973b64704738dbc8ce24d8382bb1f?s=47 Prathamesh
March 19, 2016
130

Anatomy of constant lookup and autoloading in Rails

1b0973b64704738dbc8ce24d8382bb1f?s=128

Prathamesh

March 19, 2016
Tweet

Transcript

  1. Anatomy of constant lookup & autoloading in Rails @_cha1tanya @BigBinary

  2. class PostsController < ApplicationController def index @posts = Post.all end

    end
  3. class PostsController < ApplicationController def index @posts = Post.all end

    end
  4. It just works! ™

  5. Constants in Ruby

  6. class User < ActiveRecord::Base BASE_URL = “http://example.com” end

  7. class User < ActiveRecord::Base end

  8. User = Class.new(ActiveRecord::Base)

  9. module Admin class Users < ActiveRecord::Base end end

  10. All class & module definitions create constants

  11. class PostsController < ApplicationController def index @posts = Post.all end

    end
  12. We refer to constants while using classes or modules

  13. Admin::ApiService::BASE_URL

  14. Constants are stored in modules

  15. API for handling constants

  16. Module.const_set Module.const_get Module.const_missing Module.remove_const

  17. Constant lookup in Ruby

  18. class ApiController BASE_URL = “https://github.com/api/v1” def print_location puts BASE_URL #=>

    ? end end
  19. Look for the constant inside current scope

  20. class ApiController BASE_URL = “https://github.com/api/v1” def print_location puts BASE_URL” end

    end #=> “https://github.com/api/v1
  21. module Admin BASE_URL = “https://github.com/api/v1” class ApiController def print_location puts

    BASE_URL #=> ? end end end
  22. Look for the constant in the parent namespace

  23. module Admin BASE_URL = “https://github.com/api/v1” class ApiController def print_location puts

    BASE_URL end end end #=> “https://github.com/api/v1”
  24. module SuperAdmin BASE_URL = “https://github.com/api/v1” module Admin class ApiController <

    BaseController def print_location puts BASE_URL #=> ? end end end end
  25. Keep looking in the parent namespaces

  26. module SuperAdmin BASE_URL = “https://github.com/api/v1” module Admin class ApiController <

    BaseController def print_location puts BASE_URL end end end end #=> “https://github.com/api/v1”
  27. class BaseController BASE_URL = “https://github.com/api/v1” end class ApiController < BaseController

    def print_location puts BASE_URL #=> ? end end
  28. class BaseController BASE_URL = “https://github.com/api/v1” end class ApiController < BaseController

    def print_location puts BASE_URL end end #=> “https://github.com/api/v1”
  29. Look for the constant in the ancestors chain

  30. class BaseController BASE_URL = “https://github.com/api/v2” end module Admin BASE_URL =

    “https://github.com/api/v1” class ApiController < BaseController def print_location puts BASE_URL #=> ? end end end
  31. module Admin BASE_URL = “https://github.com/api/v1” class ApiController < BaseController def

    print_location puts BASE_URL end end end #=> “https://github.com/api/v1”
  32. class BaseController BASE_URL = “https://github.com/api/v2” end module Admin class ApiController

    < BaseController def print_location puts BASE_URL end end end #=> “https://github.com/api/v2”
  33. Search in namespaces

  34. Search in namespaces Search in ancestors tree

  35. module SuperAdmin BASE_URL = “https://github.com/api/v1” module Admin BASE_URL = “https://github.com/api/v2”

    end end
  36. module SuperAdmin class Admin::ApiController def print_location puts BASE_URL #=> ?

    end end end
  37. module SuperAdmin class Admin::ApiController def print_location puts BASE_URL #=> ?

    end end end
  38. module SuperAdmin class Admin::ApiController def print_location puts BASE_URL end end

    end #=> “https://github.com/api/v1”
  39. module SuperAdmin BASE_URL = “https://github.com/api/v1” module Admin BASE_URL = “https://github.com/api/v2”

    end end
  40. Module.nesting

  41. module SuperAdmin class Admin::ApiController puts Module.nesting end end

  42. [SuperAdmin::Admin::ApiController, SuperAdmin]

  43. [SuperAdmin::Admin::ApiController, SuperAdmin]

  44. SuperAdmin::Admin::ApiController Not Found

  45. [SuperAdmin::Admin::ApiController, SuperAdmin]

  46. module SuperAdmin BASE_URL = “https://github.com/api/v1” module Admin BASE_URL = “https://github.com/api/v2”

    end end
  47. SuperAdmin::Admin

  48. SuperAdmin::Admin Not Checked!

  49. Ruby’s constant lookup algorithm**

  50. Search in current namespace

  51. Continue searching up in parent namespaces**

  52. All namespaces are exhausted

  53. Search in ancestors chain of the innermost class/module

  54. Ancestors chain is exhausted

  55. Module.const_missing(const_name)

  56. Entry point for Rails!

  57. # activesupport/lib/../dependencies.rb def const_missing(const_name) …… end

  58. Autoloading in Rails

  59. Loading mechanism

  60. require v/s load

  61. require require’s only once!

  62. load will load and execute every time!

  63. require in production

  64. require in production load in development

  65. config.cache_classes

  66. Autoload Paths

  67. config.autoload_paths

  68. app/assets app/channels app/controllers app/controllers/concerns app/helpers app/jobs app/mailers app/models app/models/concerns test/mailers/previews

  69. Similar to $LOAD_PATH in Ruby

  70. Constant lookup by Rails

  71. class PostsController < ApplicationController def index @posts = Post.all end

    end
  72. PostsController ApplicationController Post

  73. PostsController

  74. class PostsController < ApplicationController def index @posts = Post.all end

    end
  75. Constants after class or module

  76. No autoloading by Rails

  77. ApplicationController

  78. class PostsController < ApplicationController def index @posts = Post.all end

    end
  79. Top level constant

  80. app/assets/application_controller.rb

  81. app/assets/application_controller.rb app/channels/application_controller.rb

  82. app/assets/application_controller.rb app/channels/application_controller.rb app/controllers/application_controller.rb # FOUND!

  83. Post

  84. class PostsController < ApplicationController def index @posts = Post.all end

    end
  85. PostsController::Post

  86. Namespaced constants

  87. app/assets/posts_controller/post.rb app/channels/posts_controller/post.rb app/controllers/posts_controller/post.rb …

  88. app/assets/posts_controller/post app/channels/posts_controller/post app/controllers/posts_controller/post …

  89. Automatic modules

  90. module Admin end module Admin class User < ActiveRecord::Base end

    Admin::User
  91. Module acting as namespace

  92. app/models/admin

  93. app/models/admin app/models/admin/user.rb

  94. Admin = Module.new

  95. Coming back to Post

  96. app/assets/posts_controller/post app/channels/posts_controller/post app/controllers/posts_controller/post …

  97. PostsController::Post # Not Found

  98. Search for Post constant

  99. app/assets/post.rb app/channels/post.rb app/controllers/post.rb … app/models/post.rb # FOUND!

  100. Qualified constants

  101. module Admin class User < ActiveRecord::Base end end Admin::User #

    Qualified constant
  102. class User < ActiveRecord::Base end User # Top level constant

  103. Admin::User

  104. admin/user.rb

  105. admin/user.rb

  106. user.rb

  107. user.rb

  108. Admin::User

  109. Name error when not found

  110. Constant lookup by Rails

  111. Look for regular .rb file

  112. Look for automatic module

  113. Repeat search in parent namespace**

  114. The file is found, the constant is found

  115. File found, constant not defined

  116. LoadError

  117. Constant not found, File not found

  118. NameError

  119. Constant Reloading

  120. config.cache_classes = false

  121. List of monitored files

  122. config/routes.rb Locales Ruby files under autoload paths db/schema.rb & db/structure.sql

  123. Module.remove_const

  124. Wipe out all autoloader constants at the start of request

  125. @posts = Post.all

  126. Module.const_missing again!

  127. Reloading != Reloading

  128. Reloading == Wiping

  129. Convenient to wipe out & wait for autoload to kick!

  130. What happens with Gems?

  131. # config/application.rb Bundler.require(*Rails.groups)

  132. Gems are already require’d

  133. Unless require: false

  134. Gotchas

  135. Side effects of assumptions by Rails

  136. Nesting information is not passed

  137. module Admin class User < ActiveRecord::Base end end

  138. class User < ActiveRecord::Base end

  139. class Admin::UsersController def index @users = User.all end end

  140. Admin::User or User?

  141. class Admin::UsersController def index @users = User.all end end #

    app/models/user.rb # In Ruby
  142. class Admin::UsersController Module.nesting #=> [Admin::UsersController] end # app/models/user.rb # In

    Ruby
  143. class Admin::UsersController def index @users = User.all end end #

    app/models/admin/user.rb # In Rails
  144. module Admin class UsersController def index @users = User.all end

    end
  145. Prefer relative constants

  146. class Admin::UsersController def index @users = Admin::User.all end end #

    app/models/admin/user.rb
  147. Don’t mix require and autoloading

  148. require ‘user’ class Admin::UsersController def index @users = User.all end

    end
  149. Convention over configuration

  150. Follow the contract and you will be happy!

  151. Thanks! @_cha1tanya @BigBinary