Ruby on Rails: reference to ActiveRecord is a snapshot? - ruby-on-rails

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.

Related

Manually building active record objects

I have an active record query that is not using an index, resulting in app timeouts.
domain.services.where.not(parent_service_id: nil).group('services.type').select('services.type, count(services.id) as user_count')
=> [#<ServiceModel id: nil, type: "ServiceModelName">]
I have a custom sql command that forces use of the index on parent_service_id
ActiveRecord::Base.connection.execute("SELECT services.type, count(services.id) as user_count FROM services where domain_id = 21227 AND parent_service_id IS NOT NULL GROUP BY services.type")
=> {"type"=>"ServiceModelName", "user_count"=>"2810"}
Which returns the information I need quickly. However, it returns a hash instead of a model. How do I build out an activerecord object so my method can return a similar result?
Instead of connection.execute, you should just be able to do:
ServiceModel.find_by_sql(sql)

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.

New data not persisting to Rails array column on Postgres

I have a user model with a friends column of type text. This migration was ran to use the array feature with postgres:
add_column :users, :friends, :text, array: true
The user model has this method:
def add_friend(target)
#target would be a value like "1234"
self.friends = [] if self.friends == nil
update_attributes friends: self.friends.push(target)
end
The following spec passes until I add user.reload after calling #add_friend:
it "adds a friend to the list of friends" do
user = create(:user, friends: ["123","456"])
stranger = create(:user, uid: "789")
user.add_friend(stranger.uid)
user.reload #turns the spec red
user.friends.should include("789")
user.friends.should include("123")
end
This happens in development as well. The model instance is updated and has the new uid in the array, but once reloaded or reloading the user in a different action, it reverts to what it was before the add_friend method was called.
Using Rails 4.0.0.rc2 and pg 0.15.1
What could this be?
I suspect that ActiveRecord isn't noticing that your friends array has changed because, well, the underlying array reference doesn't change when you:
self.friends.push(target)
That will alter the contents of the array but the array itself will still be the same array. I know that this problem crops up with the postgres_ext gem in Rails3 and given this issue:
String attribute isn't marked as dirty, when it changes with <<
I'd expect Rails4 to behave the same way.
The solution would be to create a new array rather than trying to modify the array in-place:
update_attributes friends: self.friends + [ target ]
There are lots of ways to create a new array while adding an element to an existing array, use whichever one you like.
It looks like the issue might be your use of push, which modifies the array in place.
I can't find a more primary source atm but this post says:
One important thing to note when interacting with array (or other mutable values) on a model. ActiveRecord does not currently track "destructive", or in place changes. These include array pushing and poping, advance-ing DateTime objects. If you want to use a "destructive" update, you must call <attribute>_will_change! to let ActiveRecord know you changed that value.
If you want to use Postgresql array type, you'll have to comply with its format. From Postgresql docs the input format is
'{10000, 10000, 10000, 10000}'
which is not what friends.to_s will return. In ruby:
[1,2,3].to_s => "[1,2,3]"
That is, brackets instead of braces. You'll have to do the conversion yourself.
However I'd much rather rely on ActiveRecord serialize (see serialize). The database does not need to know that the value is actually an array, that's your domain model leaking into your database. Let Rails do its thing and encapsulate that information; it already knows how to serialize/deserialize the value.
Note: This response is applicable to Rails 3, not 4. I'll leave here in case it helps someone in the future.

Build vs new in Rails 3

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).

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