Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Rails instantiate objects retrieved from the database?

In Rails, ActiveRecord::Base.new is used for instantiating new records that aren't yet saved to the database:

new_user = User.new(name: "Bob")
new_user.new_record? # => true

So how does Rails instantiate records that are retrieved from the database? Does it use the same new method and then change values like @new_record after the fact? Or does it use some sort of special instantiation method for records retrieved from the database?

like image 922
Ajedi32 Avatar asked Feb 16 '23 09:02

Ajedi32


1 Answers

The new_record? method can be found in active_record/persistence.rb in the ActiveRecord framework and it looks like this:

def new_record?
  @new_record
end

Then if you look at active_record/core.rb in the constructor you will see this:

def initialize(attributes = nil, options = {})
  @attributes = self.class.initialize_attributes(self.class.column_defaults.deep_dup)
  @columns_hash = self.class.column_types.dup

  init_internals # here

  ensure_proper_type

  populate_with_current_scope_attributes

  assign_attributes(attributes, options) if attributes

  yield self if block_given?
  run_callbacks :initialize if _initialize_callbacks.any?
end

And if we dig a little bit deeper in the code:

def init_internals
  pk = self.class.primary_key

  @attributes[pk] = nil unless @attributes.key?(pk)

  @aggregation_cache       = {}
  @association_cache       = {}
  @attributes_cache        = {}
  @previously_changed      = {}
  @changed_attributes      = {}
  @readonly                = false
  @destroyed               = false
  @marked_for_destruction  = false
  @new_record              = true # here
  @mass_assignment_options = nil
end

As you can see, the @new_record is initialized to true by default.

However there is some case where the @new_record attribute is set to true like when you clone a record:

user = User.first
new_user = user.clone

This will call the initialize_dup method that looks like:

def initialize_dup(other) # :nodoc:
  # Code removed

  @new_record  = true

  # Code removed
  super
end

Or of course when ActiveRecord pulls records from database. I am not sure about this part, but I think that this method is called:

def init_with(coder)
  @attributes = self.class.initialize_attributes(coder['attributes'])
  @columns_hash = self.class.column_types.merge(coder['column_types'] || {})

  init_internals

  @new_record = false

  run_callbacks :find
  run_callbacks :initialize

  self
end

Which could be done like this:

post = Post.allocate
post.init_with('attributes' => { 'title' => 'hello world' })

In the first statement, it allocates memory space on the heap without calling the constructor like new would do. Then it calls the special constructor init_with.

like image 107
basgys Avatar answered Apr 28 '23 07:04

basgys