Build vs new in Rails 3 - ruby-on-rails

In the Rails 3 docs, the build method for associations is described as being the same as the new method, but with the automatic assignment of the foreign key. Straight from the docs:
Firm#clients.build (similar to Client.new("firm_id" => id))
I've read similar elsewhere.
However, when I use new (e.g. some_firm.clients.new without any parameters), the new client's firm_id association is automatically created. I'm staring at the results right now in the console!
Am I missing something? Are the docs a bit out of date (unlikely)? What's the difference between build and new?

You're misreading the docs slightly. some_firm.client.new is creating a new Client object from the clients collection, and so it can automatically set the firm_id to some_firm.id, whereas the docs are calling Client.new which has no knowledge of any Firm's id at all, so it needs the firm_id passed to it.
The only difference between some_firm.clients.new and some_firm.clients.build seems to be that build also adds the newly-created client to the clients collection:
(some_firm = Firm.new).save # Create and save a new Firm
#=> true
some_firm.clients # No clients yet
#=> []
some_firm.clients.new # Create a new client
#=> #<Client id: nil, firm_id: 1, created_at: nil, updated_at: nil>
some_firm.clients # Still no clients
#=> []
some_firm.clients.build # Create a new client with build
#=> #<Client id: nil, firm_id: 1, created_at: nil, updated_at: nil>
some_firm.clients # New client is added to clients
#=> [#<Client id: nil, firm_id: 1, created_at: nil, updated_at: nil>]
some_firm.save
#=> true
some_firm.clients # Saving firm also saves the attached client
#=> [#<Client id: 1, firm_id: 1, created_at: "2011-02-11 00:18:47", updated_at: "2011-02-11 00:18:47">]
If you're creating an object through an association, build should be preferred over new as build keeps your in-memory object, some_firm (in this case) in a consistent state even before any objects have been saved to the database.

build is just an alias for new:
alias build new
Full code can be found: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation.rb#L74

You are correct, the build and new functions have the same effect of setting the foreign key, when they are called through an association. I believe the reason the documentation is written like this is to clarify that a new Client object is being instantiated, as opposed to a new active record relationship. This is the same effect that calling .new on a class would have in Ruby. That is to say that the documentation is clarifying that calling build on an association is the same is creating a new object (calling .new) and passing the foreign keys to that object. These commands are all equivalent:
Firm.first.clients.build
Firm.first.clients.new
Client.new(:firm_id => Firm.first.id)
I believe the reason .build exists is that Firm.first.clients.new might be interpreted to mean that you are creating a new has_many relationship object, rather than an actual client, so calling .build is a way of clarifying this.

build vs new:
mostly new and build are same but build stores object in memory,
eg:
for new:
Client.new(:firm_id=>Firm.first.id)
For build:
Firm.first.clients.build
Here clients are stored in memory, when save firm , associated records are also saved.

Model.new
Tag.new post_id: 1 will instantiate a Tag with its post_id set.
#model.models.new
#post.tags.build does the same AND the instantiated Tag will be in #post.tags even before it's saved.
This means #post.save will save both the #post and the newly built tag (assuming :inverse_of is set). This is great because Rails will validate both objects before saving, and neither will be saved if either one of them fails validation.
models.new vs models.build
#post.tags.build and #post.tags.new are equivalent (at least since Rails 3.2).

Related

Ruby on Rails: reference to ActiveRecord is a snapshot?

Say I have a class User that inherits from ActiveRecord::Base, and it has a :name attribute. Then in the console I create a new user by:
> user1 = User.create(name: 'Bob')
=> #<User id: 1, name: 'Bob'>
Then I update the user's name to 'Bob'. Note I'm not doing user1.update_attribute.
> User.find_by(name: 'Bob').update_attribute(:name, 'Bill')
Now typing in:
> User.find_by(name: 'Bob')
=> nil
> User.find_by(name: 'Bill')
=> #<User id: 1, name: 'Bill'>
Which is what I expected. However, when I check my user1 reference I get:
> user1
=> #<User id: 1, name: 'Bob'>
Somehow this still has the old user name. Is user1 not a reference to the ActiveRecord? Is it a snapshot of some sort?
That is what it still has in the memory. user1.reload will do a new select and return refreshed record.
ActiveRecord fetches data from the database and converts it to objects. So yes, it's effectively a snapshot. There's just no good way to determine if any of its values changed in the meantime. While some database engines offer some sort of listen/notify, doing that for every single object is major (and useless most of the time) performance overhead.
There are alternative ORMs that adhere to "the same row is the same object" principle (such as DataMapper, see "Identity map"), but under certain circumstances that's also unreliable: the object in the meantime may have been modified by a different process your ORM has no idea about.
So ActiveRecord offers a compromise: it caches the object, but leaves you with reload method, that refreshes the object with data from the database in-place, returning itself, so you can fetch it and act on it without extra assignments.
Your user1 is not updated automatically. You need to call user1.reload first.

How to reference a database column in rails active record interface

Really confused.
I've been reading through the rails guide and there seem to be difference ways to reference a column in rails. Sometimes it's as a string eg
Client.select("viewable_by, locked")
and sometimes with a colon at the beginning
Client.select(:name).uniq
When do I decide which to use? Are these interchangeable?
If it is a string, it will be injected into the sql statement, which allows you to do some advanced SQL-y stuff, like
select("count(name) as name_count, nvl(price, 'N/A')")
(this is just an example, mashing some stuff together)
If you want to select some columns, I would always use symbols or an array of symbols, like
Client.select(:viewable_by, :locked)
So in short: yes they are interchangeable, the string will not be parsed, just injected into the SQL statement, which allows you do some more advanced stuff if needed.
Yes, they are interchangeable. They return the same result - i.e an ActiveRecord Relation containing instances of models with only the fields you requested set.
Client.select("viewable_by, locked")
=> [#<Client id: nil, viewable_by: "admin", locked: true>]
Client.select(:viewable_by, :locked)
=> [#<Client id: nil, viewable_by: "admin", locked: true>]
I've made up the responses but you get the idea.
If you simply wanted an array of the names I'd use pluck instead:
Client.pluck(:name).uniq

Mongoid::Versioning - delete one version

I've a Rails model set up with Mongoid::Versioning.
class Post
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Versioning
field :name, type: String
end
I want to delete a specific older version. For eg.
post = Post.create name: 'A'
post.update_attributes name: 'B'
post.update_attributes name: 'C'
post.version #=> 3
post.versions.count #=> 2
first_version = post.versions.first #=> #<Post _id: , created_at: _, updated_at: _, version: 2, name: "B">
I want to delete first_version, but when I try to delete it..
first_version.delete
post.versions.count #=> 1
post.reload
post.versions.count #=> 0
..all versions get deleted.
I've tried using destroy instead, tried running the code inside a block passed to post.versionless, to no avail. What should I do?
UPDATE:
I've gotten it to work with Mongoid::Paranoia. But it'd be nice to have the flexibility of not using Mongoid::Paranoia.
The concept of deleting intermediate versions is incorrect and corrupts the case for versioning. If you delete intermediate versions, then you are never sure if it's intentional or some corruption.
In spite of this, if you really want to do this, you should change to code to the following.
deleted = post.versions.first
post.versions.delete(deleted)
I am not sure why you want to do this though. If you want to ensure that you don't have too many versions and want to clean up, use max_versions class method.
Post.max_versions(5)
If you want to avoid versioning in some cases, use versionless
post.versionless(&:save)

Rails first_or_create ActiveRecord method

What does the first_or_create / first_or_create! method do in Rails?
According to the documentation, the method "has no description"...
From the Guides
first_or_create
The first_or_create method checks whether first returns nil or not. If it does return nil, then create is called. This is very powerful when coupled with the where method. Let’s see an example.
Suppose you want to find a client named ‘Andy’, and if there’s none, create one and additionally set his locked attribute to false. You can do so by running:
Client.where(:first_name => 'Andy').first_or_create(:locked => false)
# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">
The SQL generated by this method looks like this:
SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1
BEGIN
INSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 0, NULL, '2011-08-30 05:22:57')
COMMIT
first_or_create returns either the record that already exists or the new record. In our case, we didn’t already have a client named Andy so the record is created and returned.
first_or_create!
You can also use first_or_create! to raise an exception if the new record is invalid. Validations are not covered on this guide, but let’s assume for a moment that you temporarily add
validates :orders_count, :presence => true
to your Client model. If you try to create a new Client without passing an orders_count, the record will be invalid and an exception will be raised:
Client.where(:first_name => 'Andy').first_or_create!(:locked => false)
# => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank
I believe first_or_create should generally be avoided these days. (Although it's still in Rails 6.1.1.) In 4.0 they added find_or_create_by and friends, which are apparently meant to replace the first_or_create methods. first_or_create was once mentioned in the guides, now it's not. And it is no longer documented in the code (since Rails 4.0). It was introduced in Rails 3.2.19.
The reasons are:
It might seem that first_or_create(attrs) does first(attrs) || create(attrs), where in fact it does first || create(attrs). The proper usage is:
Model.where(search_attrs).first_or_create(create_attrs)
That is, it might confuse people. first(attrs) || create(attrs) is what find_or_create_by does.
Also, using first_or_create introduces a scope, that might affect the create callbacks in an unexpected way.
More on it in the changelog (search for first_or_create).
Gets the first record that matches what you have specified or creates one if there are no matches
If you check the source, you will see that they are almost identical. The only difference is that the first one calls the "create" method and the other one "create!". This means that the second one will raise an exception, if the creation is not successful.

How to get ar_fixtures to load protected attributes?

I'm using ar_fixtures to seed data in a rails project (the procedure was written before seed arrived in 2.3.4)
It is all working fine, except I find that one column in particular is not getting populated (user_id). user_id is set in the yaml file, for example:
- !ruby/object:Service
attributes:
name: name
updated_at: 2009-10-14 11:50:36
provider_id: "1"
id: "1"
description: ""
user_id: "1"
created_at: 2009-10-14 11:47:01
attributes_cache: {}
But even though the related user object exists when running the import (Service.load_from_file), user_id is nil after import. Other foreign keys (like provider_id in the example above) are loaded correctly.
I am suspecting this is because user_id is protected in the model and this getting blocked during mass assignment.
Does anyone know if this is the case, and if so, how to get around the mass assignment protection? Of course, I want to leave mass assignment protection in place for the application at runtime.
Fixed! Answering my own question..
Simply requires a tweak of the attr_protected / attr_accessible setting prior to loading. e.g.
Service.attr_protected.delete "user_id"
Service.load_from_file
Or if the restriction is based on attr_accessible:
Service.attr_accessible :user_id
Service.load_from_file

Resources