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

Demystifying some of the magic behind Rails

Ridhwana Khan
September 27, 2024

Demystifying some of the magic behind Rails

Rails is renowned for its elegance, productivity, and “magic” that simplifies web development. As a technical writer for the official Ruby on Rails guides, I’ve had the opportunity to dive into the “magic” of Rails to enhance my understanding of the source code and provide clear explanations in the guides.

In this talk, I’ll share my journey of inspecting modules, exploring concepts, and sharing insights gained from my experience to help attendees better understand how some Rails components work internally. Whether you are a beginner eager to understand the framework’s foundations or an experienced developer looking to deepen your knowledge, this dive into the source code will enhance your proficiency and demystify the framework’s inner workings.

Ridhwana Khan

September 27, 2024
Tweet

Other Decks in Technology

Transcript

  1. 2. ✍ Writing & Team Review 1. 💻 Audit 3.

    🔎 PR & Community Review Three Step Process:
  2. What Can You Expect? 1. Navigating the Source Code: Explore

    strategies to trace through the Rails
  3. What Can You Expect? 1. Navigating the Source Code: Explore

    strategies to trace through the Rails 2. Examples: Tracing through three different modules in Rails
  4. What Can You Expect? 1. Navigating the Source Code: Explore

    strategies to trace through the Rails 2. Examples: Tracing through three different modules in Rails 3. Metaprogramming Evaluation: Benefits & Drawbacks of metaprogramming within the Rails source
  5. What Can You Expect? 1. Navigating the Source Code: Explore

    strategies to trace through the Rails 2. Examples: Tracing through three different modules in Rails 3. Metaprogramming Evaluation: Benefits & Drawbacks of metaprogramming within the Rails source 4. Using these Patterns in the Wild: Apply these techniques to different use cases
  6. Patterns & Conventions 236 results - 109 files actioncable/lib/action_cable/connection/tagged_logger_proxy.rb: 35

    %i( debug info warn error fatal unknown ).each do |severity| 36: define_method(severity) do |message = nil, &block| 37 log severity, message, &block actionpack/lib/abstract_controller/callbacks.rb: 230 [:before, :after, :around].each do |callback| 231: define_method "#{callback}_action" do |*names, &blk| 232 _insert_callbacks(names, blk) do |name, options| 236 237: define_method "prepend_#{callback}_action" do |*names, &blk| 238 _insert_callbacks(names, blk) do |name, options| 244 # the allowed parameters. 245: define_method "skip_#{callback}_action" do |*names| 246 _insert_callbacks(names) do |name, options| actionpack/lib/abstract_controller/railties/routes_helpers.rb: 11 Module.new do 12: define_method(:inherited) do |klass| 13 super(klass) actionpack/lib/action_controller/metal/flash.rb: 37 38: define_method(type) do 39 request.flash[type] actionpack/lib/action_controller/metal/renderers.rb: 75 def self.add(key, &block) 76: define_method(_render_with_renderer_method_name(key), &block) 77 RENDERERS << key.to_sym actionpack/lib/action_dispatch/http/content_security_policy.rb: 187 DIRECTIVES.each do |name, directive| 188: define_method(name) do |*sources| 189 if sources.first actionpack/lib/action_dispatch/http/permissions_policy.rb: 122 DIRECTIVES.each do |name, directive| 123: define_method(name) do |*sources| 124 if sources.first actionpack/lib/action_dispatch/routing/mapper.rb: 710 711: define_method :find_script_name do |options| 712 if options.key?(:script_name) && options[:script_name].present? actionpack/lib/action_dispatch/routing/route_set.rb: 335 def define_url_helper(mod, name, helper, url_strategy) 336: mod.define_method(name) do |*args| 337 last = args.last 517 MountedHelpers.class_eval do 518: define_method "_#{name}" do 519 RoutesProxy.new(routes, _routes_context, helpers, script_namer) 616 # extra conveniences for working with @_routes. 617: define_method(:_routes) { @_routes || routes }
  7. Testing Suite class Pirate < ActiveRecord::Base belongs_to :parrot, validate: true

    has_one :ship has_many :birds end class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCase def setup super @pirate = Pirate.new end test "should generate validation methods for has_many associations" do assert_respond_to @pirate, :validate_associated_records_for_birds end test "should generate validation methods for has_one associations with :validate => true" do assert_respond_to @pirate, :validate_associated_records_for_ship end test "should generate validation methods for belongs_to associations with :validate => true" do assert_respond_to @pirate, :validate_associated_records_for_parrot end end
  8. Testing Suite class Pirate < ActiveRecord::Base belongs_to :parrot, validate: true

    has_one :ship has_many :birds end class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCase def setup super @pirate = Pirate.new end test "should generate validation methods for has_many associations" do assert_respond_to @pirate, :validate_associated_records_for_birds end test "should generate validation methods for has_one associations with :validate => true" do assert_respond_to @pirate, :validate_associated_records_for_ship end test "should generate validation methods for belongs_to associations with :validate => true" do assert_respond_to @pirate, :validate_associated_records_for_parrot end end
  9. What does this module do? class ArticlesController < ApplicationController def

    show @article = Article.find(params[:id]) respond_to do |format| format.html # renders show.html.erb format.json { render json: @article } end end end GET /articles/1.json Accept: application/json
  10. Example Code module AbstractController module Collector def self.generate_method_for_mime(mime) sym =

    mime.is_a?(Symbol) ? mime : mime.to_sym class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{sym}(...) custom(Mime[:#{sym}], ...) end RUBY end Mime::SET.each do |mime| generate_method_for_mime(mime) end Mime::Type.register_callback do |mime| generate_method_for_mime(mime) unless instance_methods.include?(mime.to_sym) end private def method_missing(symbol, ...) unless mime_constant = Mime[symbol] raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \ "https://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \ "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \ "be sure to nest your variant response within a format response: " \ "format.html { |html| html.tablet { ... } }" end if Mime::SET.include?(mime_constant) AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) else super end end end end generate_method_for_mime method_missing
  11. module AbstractController module Collector def self.generate_method_for_mime(mime) ... end ... private

    def method_missing(symbol, ...) unless mime_constant = Mime[symbol] raise NoMethodError, "To respond to a custom format, ..." end if Mime::SET.include?(mime_constant) AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) else super end end end end GET /articles/1.json Accept: application/json
  12. module AbstractController module Collector def self.generate_method_for_mime(mime) ... end ... private

    def method_missing(symbol, ...) unless mime_constant = Mime[symbol] raise NoMethodError, "To respond to a custom format, ..." end if Mime::SET.include?(mime_constant) AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) else super end end end end GET /articles/1.json Accept: application/json def json ... end
  13. module AbstractController module Collector def self.generate_method_for_mime(mime) ... end ... private

    def method_missing(symbol, ...) unless mime_constant = Mime[symbol] raise NoMethodError, "To respond to a custom format, ..." end if Mime::SET.include?(mime_constant) AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) else super end end end end :json GET /articles/1.json Accept: application/json
  14. module AbstractController module Collector def self.generate_method_for_mime(mime) ... end ... private

    def method_missing(symbol, ...) unless mime_constant = Mime[symbol] raise NoMethodError, "To respond to a custom format, ..." end if Mime::SET.include?(mime_constant) AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) else super end end end end :json GET /articles/1.json Accept: application/json
  15. module AbstractController module Collector def self.generate_method_for_mime(mime) sym = mime.is_a?(Symbol) ?

    mime : mime.to_sym class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{sym}(...) custom(Mime[:#{sym}], ...) end RUBY end ... private def method_missing(symbol, ...) ... AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) ... end end end GET /articles/1.json Accept: application/json
  16. module AbstractController module Collector def self.generate_method_for_mime(mime) sym = mime.is_a?(Symbol) ?

    mime : mime.to_sym class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{sym}(...) custom(Mime[:#{sym}], ...) end RUBY end ... private def method_missing(symbol, ...) ... AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) ... end end end GET /articles/1.json Accept: application/json
  17. module AbstractController module Collector def self.generate_method_for_mime(mime) sym = mime.is_a?(Symbol) ?

    mime : mime.to_sym class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{sym}(...) custom(Mime[:#{sym}], ...) end RUBY end ... private def method_missing(symbol, ...) ... AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) ... end end end def :json(…) custom(Mime[:json], ...) end GET /articles/1.json Accept: application/json :json
  18. module AbstractController module Collector def self.generate_method_for_mime(mime) sym = mime.is_a?(Symbol) ?

    mime : mime.to_sym class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{sym}(...) custom(Mime[:#{sym}], ...) end RUBY end ... private def method_missing(symbol, ...) ... AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) ... end end end GET /articles/1.json Accept: application/json
  19. module AbstractController module Collector def self.generate_method_for_mime(mime) sym = mime.is_a?(Symbol) ?

    mime : mime.to_sym class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{sym}(...) custom(Mime[:#{sym}], ...) end RUBY end ... private def method_missing(symbol, ...) ... AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) ... end end end GET /articles/1.json Accept: application/json :json def json(...) custom(Mime[:json], ...) end def json ... end
  20. module AbstractController module Collector def self.generate_method_for_mime(mime) sym = mime.is_a?(Symbol) ?

    mime : mime.to_sym class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{sym}(...) custom(Mime[:#{sym}], ...) end RUBY end ... private def method_missing(symbol, ...) ... AbstractController::Collector.generate_method_for_mime(mime_constant) public_send(symbol, ...) ... end end end GET /articles/1.json Accept: application/json def json(...) custom(Mime[:json], ...) end { Mime[:json] => Proc.new { render json: @article } } :json def json ... end
  21. What does this module do? class ArticlesController < ApplicationController def

    show @article = Article.find(params[:id]) respond_to do |format| format.html # renders show.html.erb format.json { render json: @article } end end end GET /articles/1.json Accept: application/json { Mime[:json] => Proc.new { render json: @article } }
  22. Examples class Post < ActiveRecord::Base belongs_to :author end post =

    Post.find(7) author = Author.find(19) post.author # similar to Author.find(post.author_id) post.author = author # similar to post.author_id = author.id post.build_author # similar to post.author = Author.new post.create_author # similar to post.author = Author.new; post.author.save; post.author post.create_author! # similar to post.author = Author.new; post.author.save!; post.author post.reload_author post.reset_author post.author_changed? post.author_previously_changed?
  23. Examples class Post < ActiveRecord::Base belongs_to :author end post =

    Post.find(7) author = Author.find(19) post.author # similar to Author.find(post.author_id) post.author = author # similar to post.author_id = author.id post.build_author # similar to post.author = Author.new post.create_author # similar to post.author = Author.new; post.author.save; post.author post.create_author! # similar to post.author = Author.new; post.author.save!; post.author post.reload_author post.reset_author post.author_changed? post.author_previously_changed?
  24. module ActiveRecord::Associations::Builder # :nodoc: class Association # :nodoc: ... def

    self.build(model, name, scope, options, &block) if model.dangerous_attribute_method?(name) raise ArgumentError, "You tried to define an association named #{name} on the model " \ "#{model.name}, but this will conflict with a method #{name} " \ "already defined by Active Record. Please choose a different " \ "association name." end reflection = create_reflection(model, name, scope, options, &block) define_accessors(model, reflection) define_callbacks(model, reflection) define_validations(model, reflection) define_change_tracking_methods(model, reflection) reflection end ... end end
  25. But first, what is a Reflection? class Post < ApplicationRecord

    belongs_to :author has_many :comments end A re fl ection in Rails represents the metadata about an association between two Active Record models. It contains details such as the type of association, the associated class, & options like foreign key constraints.
  26. But first, what is a Reflection? class Post < ApplicationRecord

    belongs_to :author has_many :comments end A re fl ection in Rails represents the metadata about an association between two Active Record models. It contains details such as the type of association, the associated class, & options like foreign key constraints. reflection = Post.reflect_on_association(:author) puts reflection.macro #=> :belongs_to puts reflection.class_name #=> "Author"
  27. module ActiveRecord::Associations::Builder # :nodoc: class Association # :nodoc: ... def

    self.build(model, name, scope, options, &block) if model.dangerous_attribute_method?(name) raise ArgumentError, "You tried to define an association named #{name} on the model " \ "#{model.name}, but this will conflict with a method #{name} " \ "already defined by Active Record. Please choose a different " \ "association name." end reflection = create_reflection(model, name, scope, options, &block) define_accessors(model, reflection) define_callbacks(model, reflection) define_validations(model, reflection) define_change_tracking_methods(model, reflection) reflection end ... end end
  28. module ActiveRecord::Associations::Builder # :nodoc: class Association # :nodoc: ... def

    self.build(model, name, scope, options, &block) ... reflection = create_reflection(model, name, scope, options, &block) define_accessors(model, reflection) define_callbacks(model, reflection) define_validations(model, reflection) define_change_tracking_methods(model, reflection) reflection end ... end end define_accessors post.author post.author = author class Post < ApplicationRecord belongs_to :author end
  29. module ActiveRecord::Associations::Builder # :nodoc: class Association # :nodoc: # Defines

    the setter & getter methods for the association # class Post < ActiveRecord::Base # has_many :comments # end # # Post.first.comments & Post.first.comments= methods are defined by this method... def self.define_accessors(model, reflection) mixin = model.generated_association_methods name = reflection.name define_readers(mixin, name) define_writers(mixin, name) end end end class Post < ApplicationRecord belongs_to :author end post.author post.author = author Post::GeneratedAssociationMethods define_accessors
  30. module ActiveRecord::Associations::Builder # :nodoc: class Association # :nodoc: # Defines

    the setter & getter methods for the association # class Post < ActiveRecord::Base # has_many :comments # end # # Post.first.comments & Post.first.comments= methods are defined by this method... def self.define_accessors(model, reflection) mixin = model.generated_association_methods name = reflection.name define_readers(mixin, name) define_writers(mixin, name) end end end define_readers(Post::GeneratedAssociationMethods, "author") define_writers(Post::GeneratedAssociationMethods, "author") define_accessors class Post < ApplicationRecord belongs_to :author end post.author post.author = author Post::GeneratedAssociationMethods
  31. module ActiveRecord::Associations::Builder class Association def self.define_accessors(model, reflection) mixin = model.generated_association_methods

    name = reflection.name define_readers(mixin, name) define_writers(mixin, name) end def self.define_readers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name} association(:#{name}).reader end CODE end def self.define_writers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}=(value) association(:#{name}).writer(value) end CODE end end end post.author post.author = author
  32. module ActiveRecord::Associations::Builder class Association ... def self.define_readers(mixin, name) mixin.class_eval <<-CODE,

    __FILE__, __LINE__ + 1 def #{name} association(:#{name}).reader end CODE end def self.define_writers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}=(value) association(:#{name}).writer(value) end CODE end end end def author association(:author).reader end post.author post.author = author Post::GeneratedAssociationMethods "author"
  33. module ActiveRecord::Associations::Builder class Association ... def self.define_readers(mixin, name) mixin.class_eval <<-CODE,

    __FILE__, __LINE__ + 1 def #{name} association(:#{name}).reader end CODE end def self.define_writers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}=(value) association(:#{name}).writer(value) end CODE end end end def author= author association(:author).writer(author) end "author" post.author post.author = author Post::GeneratedAssociationMethods
  34. module ActiveRecord::Associations::Builder class Association ... def self.define_readers(mixin, name) mixin.class_eval <<-CODE,

    __FILE__, __LINE__ + 1 def #{name} association(:#{name}).reader end CODE end def self.define_writers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}=(value) association(:#{name}).writer(value) end CODE end end end def author= author association(:author).writer(author) end post.author post.author = author def author association(:author).reader end
  35. # rails/activerecord/lib/active_record/associations/builder/belongs_to.rb module ActiveRecord::Associations::Builder # :nodoc: class BelongsTo < SingularAssociation

    # :nodoc: ... def self.define_change_tracking_methods(model, reflection) model.generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{reflection.name}_changed? association(:#{reflection.name}).target_changed? end def #{reflection.name}_previously_changed? association(:#{reflection.name}).target_previously_changed? end CODE end ... end end Define Change Tracking Methods post.author_changed? post.author_previously_changed? def author_changed? association(:author).target_changed? end def author_previously_changed? association(:author).target_previously_changed? end
  36. # rails/activerecord/lib/active_record/associations/builder/singular_association.rb module ActiveRecord::Associations::Builder # :nodoc: class SingularAssociation < Association

    # :nodoc: ... def self.define_accessors(model, reflection) ... mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def reload_#{name} association(:#{name}).force_reload_reader end def reset_#{name} association(:#{name}).reset end CODE end ... end end post.reload_author post.reset_author def reload_author association(:author).force_reload_reader end def reset_author association(:author).reset end Define Reload & Reset Methods
  37. # rails/activerecord/lib/active_record/associations/builder/singular_association.rb module ActiveRecord::Associations::Builder # :nodoc: class SingularAssociation < Association

    # :nodoc: ... # Defines the (build|create)_association methods for # belongs_to or has_one association def self.define_constructors(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def build_#{name}(*args, &block) association(:#{name}).build(*args, &block) end def create_#{name}(*args, &block) association(:#{name}).create(*args, &block) end def create_#{name}!(*args, &block) association(:#{name}).create!(*args, &block) end CODE end ... end end post.build_author post.create_author post.create_author! def build_author(*args, &block) association(:author).build(*args, &block) end def create_author(*args, &block) association(:author).create(*args, &block) end Define Constructors def create_author!(*args, &block) association(:author).create(*args, &block) end
  38. module ActiveRecord module AutosaveAssociation module AssociationBuilderExtension # :nodoc: def self.build(model,

    reflection) ... end ... end module ClassMethods # :nodoc: private ... def define_autosave_validation_callbacks(reflection) ... end ... end end end end class Post < ApplicationRecord belongs_to :author end
  39. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_autosave_validation_callbacks(reflection) validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) if reflection.collection? method = :validate_collection_association elsif reflection.has_one? method = :validate_has_one_association else method = :validate_belongs_to_association end define_non_cyclic_method(validation_method) { send(method, reflection) } validate validation_method after_validation :_ensure_no_duplicate_errors end end ... end end end end class Post < ApplicationRecord belongs_to :author end <ActiveRecord::Reflection::BelongsToReflection:0x00007fffcac51b60>
  40. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_autosave_validation_callbacks(reflection) validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) if reflection.collection? method = :validate_collection_association elsif reflection.has_one? method = :validate_has_one_association else method = :validate_belongs_to_association end define_non_cyclic_method(validation_method) { send(method, reflection) } validate validation_method after_validation :_ensure_no_duplicate_errors end end ... end end end end class Post < ApplicationRecord belongs_to :author end
  41. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_autosave_validation_callbacks(reflection) validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) if reflection.collection? method = :validate_collection_association elsif reflection.has_one? method = :validate_has_one_association else method = :validate_belongs_to_association end define_non_cyclic_method(validation_method) { send(method, reflection) } validate validation_method after_validation :_ensure_no_duplicate_errors end end ... end end end end class Post < ApplicationRecord belongs_to :author end :validate_associated_ records_for_author
  42. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_autosave_validation_callbacks(reflection) validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) if reflection.collection? method = :validate_collection_association elsif reflection.has_one? method = :validate_has_one_association else method = :validate_belongs_to_association end define_non_cyclic_method(validation_method) { send(method, reflection) } validate validation_method after_validation :_ensure_no_duplicate_errors end end ... end end end end send(:validate_belongs_to_association, <ActiveRecord::Reflection::BelongsToRef lection:0x00007fffcac51b60>) class Post < ApplicationRecord belongs_to :author end :validate_associated_ records_for_author
  43. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_autosave_validation_callbacks(reflection) validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) if reflection.collection? method = :validate_collection_association elsif reflection.has_one? method = :validate_has_one_association else method = :validate_belongs_to_association end define_non_cyclic_method(validation_method) { send(method, reflection) } validate validation_method after_validation :_ensure_no_duplicate_errors end end ... end end end end send(:validate_belongs_to_association, <ActiveRecord::Reflection::BelongsToRef lection:0x00007fffcac51b60>) class Post < ApplicationRecord belongs_to :author end :validate_associated_ records_for_author
  44. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_non_cyclic_method(name, &block) return if method_defined?(name, false) define_method(name) do |*args| result = true; @_already_called ||= {} # Loop prevention for validation of associations unless @_already_called[name] begin @_already_called[name] = true result = instance_eval(&block) ensure @_already_called[name] = false end end result end end ... end end end end class Post < ApplicationRecord belongs_to :author end
  45. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_non_cyclic_method(name, &block) return if method_defined?(name, false) define_method(name) do |*args| result = true; @_already_called ||= {} # Loop prevention for validation of associations unless @_already_called[name] begin @_already_called[name] = true result = instance_eval(&block) ensure @_already_called[name] = false end end result end end ... end end end end :validate_associated_ records_for_author class Post < ApplicationRecord belongs_to :author end
  46. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_non_cyclic_method(name, &block) return if method_defined?(name, false) define_method(name) do |*args| result = true; @_already_called ||= {} # Loop prevention for validation of associations unless @_already_called[name] begin @_already_called[name] = true result = instance_eval(&block) ensure @_already_called[name] = false end end result end end ... end end end end define_method(:validate_associated_records_for_author) class Post < ApplicationRecord belongs_to :author end
  47. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_non_cyclic_method(name, &block) return if method_defined?(name, false) define_method(name) do |*args| result = true; @_already_called ||= {} # Loop prevention for validation of associations unless @_already_called[name] begin @_already_called[name] = true result = instance_eval(&block) ensure @_already_called[name] = false end end result end end ... end end end end class Post < ApplicationRecord belongs_to :author end
  48. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_non_cyclic_method(name, &block) return if method_defined?(name, false) define_method(name) do |*args| result = true; @_already_called ||= {} # Loop prevention for validation of associations unless @_already_called[name] begin @_already_called[name] = true result = instance_eval(&block) ensure @_already_called[name] = false end end result end end ... end end end end class Post < ApplicationRecord belongs_to :author end
  49. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_non_cyclic_method(name, &block) return if method_defined?(name, false) define_method(name) do |*args| result = true; @_already_called ||= {} # Loop prevention for validation of associations unless @_already_called[name] begin @_already_called[name] = true result = instance_eval(&block) ensure @_already_called[name] = false end end result end end ... end end end end result = instance_eval { send(:validate_belongs_to_association, <ActiveRecord::Reflection::BelongsToReflection:0x00007f ffcac51b60>) } class Post < ApplicationRecord belongs_to :author end
  50. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_autosave_validation_callbacks(reflection) validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) if reflection.collection? method = :validate_collection_association elsif reflection.has_one? method = :validate_has_one_association else method = :validate_belongs_to_association end define_non_cyclic_method(validation_method) { send(method, reflection) } validate validation_method after_validation :_ensure_no_duplicate_errors end end ... end end end end validate :validate_associated_records_for_author class Post < ApplicationRecord belongs_to :author end
  51. module ActiveRecord module AutosaveAssociation module ClassMethods # :nodoc: private def

    define_autosave_validation_callbacks(reflection) validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) if reflection.collection? method = :validate_collection_association elsif reflection.has_one? method = :validate_has_one_association else method = :validate_belongs_to_association end define_non_cyclic_method(validation_method) { send(method, reflection) } validate validation_method after_validation :_ensure_no_duplicate_errors end end ... end end end end validate :validate_associated_records_for_author class Post < ApplicationRecord belongs_to :author end
  52. class Profile < ApplicationRecord belongs_to :user validates :user_id, uniqueness: true

    validates :location, :website_url, length: { maximum: 100 } validates :website_url, url: { allow_blank: true, no_local: true, schemes: %w[https http] } validates_with ProfileValidator ATTRIBUTE_NAME_REGEX = /(?<attribute_name>\w+)=?/ CACHE_KEY = "profile/attributes".freeze # Static fields are columns on the profiles table; they have no relationship # to a ProfileField record. These are columns we can safely assume exist for # any profile on a given Forem. STATIC_FIELDS = %w[summary location website_url].freeze # Update the Rails cache with the currently available attributes. def self.refresh_attributes! Rails.cache.delete(CACHE_KEY) attributes end def self.attributes Rails.cache.fetch(CACHE_KEY, expires_in: 24.hours) do ProfileField.pluck(:attribute_name) end end def self.static_fields STATIC_FIELDS end def clear! update(data: {}) end # Lazily add accessors for profile fields on first use def method_missing(method_name, *args, **kwargs, &block) match = method_name.match(ATTRIBUTE_NAME_REGEX) super unless match field = ProfileField.find_by(attribute_name: match[:attribute_name]) super unless field self.class.instance_eval do store_accessor :data, field.attribute_name.to_sym end public_send(method_name, *args, **kwargs, &block) end # Defining this is not only a good practice in general, it's also necessary # for `update` to work since the `_assign_attribute` helper it uses performs # an explicit `responds_to?` check. def respond_to_missing?(method_name, include_private = false) match = method_name.match(ATTRIBUTE_NAME_REGEX) return true if match && match[:attribute_name].in?(self.class.attributes) super end end https://github.com/forem/forem/ blob/main/app/models/profile.rb