How to find existence of association with certain attributes with optimal SQL? - ruby-on-rails

Suppose you have two ActiveRecord models - Problem and ProblemSet.
You have a #problem_set object, and you want to check if it has a problem with a certain title attribute.
You could say:
#problem_set.problems.where(:title => "Adding Numbers").any?
which returns true, by running the optimal SQL:
SELECT COUNT(*) FROM "problems" INNER JOIN "problem_sets_problems" ON "problems"."id" = "problem_sets_problems"."problem_id" WHERE "problem_sets_problems"."problem_set_id" = 1 AND "problems"."title" = 'Adding Numbers'
However, if #problem_set was in memory, ie, you got #problem_set by:
#problem_set = ProblemSet.new()
#problem_set.problems << Problem.new(:title = "Adding Numbers")
Then you will not be able to find the problem (ie. it returns false!). This is because the following SQL is run:
SELECT COUNT(*) FROM "problems" INNER JOIN "problem_sets_problems" ON "problems"."id" = "problem_sets_problems"."problem_id" WHERE "problem_sets_problems"."problem_set_id" IS NULL AND "problems"."title" = 'Adding Numbers'
A possible way to perform the check correctly for both persistent objects and in-memory objects, is:
#problem_set.problems.map(&:title).include? "Adding Numbers"
Which correctly returns true in both cases. However, in the case of a persistent object, it runs the non-optimal SQL (which retrieves all problems):
SELECT "problems".* FROM "problems" INNER JOIN "problem_sets_problems" ON "problems"."id" = "problem_sets_problems"."problem_id" WHERE "problem_sets_problems"."problem_set_id" = 1
Question: Is there a way to use the same code to check for both persistent objects and in-memory objects, while running optimal SQL code?
Note that a solution which checks for object persistence is permitted (but I don't see how to check the dirtiness of the collection). However, it should still work if a persistent object is modified (ie. the association collection attribute becomes dirty, and therefore the result from an SQL query would be out-of-date).

Ok, I finally worked it out.
Browsing through obscure rails functions, I found the persisted? method. You can use #problem_set.persisted? to check if the object is persistent or in-memory only.
So the answer is:
if #problem_set.persisted?
#problem_set.problems.where(:title => "Adding Numbers").any?
else
#problem_set.problems.map(&:title).include? "Adding Numbers"
end
The remaining question is, what about persistent objects where the association collection is dirty? Well, by experimentation, I found out that it doesn't really happen. When you add an object to the collection, for example, one of the following:
#problem_set.problems << Problem.new(:title => "hello")
#problem_set.problems.push Problem.new(:title => "hello")
ActiveRecord immediately saves the data. Similarly, it immediately destroys the row from the associations table when you say:
#problem_set.problems.delete(#problem_set.problems[2])
That means, although there is no such method as #problem_set.problems_changed?, if there was, the current implementation would result in problems_changed? always returning false.
In effect, the collection<<, collection.push, collection.delete methods auto-save (ie. calls save automatically).

Related

Rails - finding an object in an active record association by an attribute without issuing more queries

Very short question, I feel like the answer must be already on StackOverflow but I couldn't find it.
I have some incoming parameters. They each have a unique ID.
I query the database and get an active record association using something like:
existing_things = current_user.things.where(uuid: [param_uuids])
This gives me an Active Record Association of those objects.
However, what I later do is something like:
existing_things.where(uuid: some_specific_uuid)
Of course, when I run the above query, it issues an SQL statement.
What I would love to do is find an object in a pre-loaded Active Record array of objects, and return that object without issuing another query.
How should I do that?
The same way you would if it was an ordinary array: Enumerable#find.
existing_things.find { |t| t.uuid == some_specific_uuid }
(or select if you're expecting more than one match)
If you're doing a lot of lookups like that, you might want to get a Hash involved:
things_by_uuid = existing_things.index_by(&:uuid)
things_by_uuid[some_specific_uuid]
(again, if you're expecting more than one match per value, there's group_by)

How to safely modify in memory mongoid model?

I'm using mongoid gem on rail project and I'm quite puzzled by trying to modify the model in memory but never saving it so I do not modify the db. I'm trying to modify an attribute from a model loaded in memory but it does not work as shown bellow:
mymodel = MyModel.where('some criteria')
mymodel.first.some_attribute = 0
mymodel.first.some_attribute == 0 -> is false
So I guess mongo reloads from the db each time we do first or even looping on each entry and setting some attribute has no effect, if I loop again all attributes I set are still set to the original value. Is there a way to commit the transaction and force mymodel to stay loaded in memory? It's hard for me to use proper terminology so I hope you get what I'm saying.
Calling first is a query so this is two distinct queries:
M.first
M.first
and two hits to the database that produce two completely different model instances. Similarly, calling M.each { ... } (or some other iteration method) twice will hit the database twice and produce two sets of completely distinct model instances. You could have a look at what #object_id says to verify this.
If you want to load the objects and do things to them then be explicit about it:
m = M.first
m.attr = 0
# Now m.attr == 0 will be true and you can m.save to update the database
and for iterating, you can call #to_a to execute the query and pull a bunch of model instances from the database into local memory:
ms = M.some_query.to_a
ms.each { ... }
ms.each { ... } # iterates over the same model instances as the first ms.each

Postgres/Rails => is object guaranteed to be persisted if I do .save?

If I have a model and save it like this:
model = Website.new
model.attr = 1
model.id = 1
model.save #assume no errors in saving
then retrieve it like this:
model2 = Website.find(1)
Will model2 always be returned? Ignoring errors saving to the database.
Is there a possible scenario where the data is not yet committed to the database, and as a result the find results in no records found? Do I need to delay the find to guarantee the row is returned?
Assuming no database errors, and assuming you haven't overwritten save on Website, the only race condition you'd have is if you try to access the object (via find or otherwise) in the milliseconds before the record is created in the database.
So, to directly answer your question - yes, it's possible - but given a single database (e.g. no read-only slaves or anything like that), it's highly, highly unlikely.

Rails Associations push (collection<<)

Does pushing an entity to a it's parent collection causes the collection to be loaded?
e.g.:
Parent.childs << a_child
Is the parent.childs "array" now loaded with ALL the childs or just "a_child" ? i.e. will a SQL "select * from childs where parent_id = ?" statement will be executed before adding to the collection?
EDIT:
http://apidock.com/rails/v3.2.3/ActiveRecord/Associations/CollectionAssociation/concat_records seems to call "add_target" BEFORE calling "insert_record" which adds the entity to the #target array...
No (thank god)! Parent.childs is a Proxy and '<<' will just trigger the creation/update of a_child
More info on How do rails association methods work?
Also in the rails comments of associations/collection_associations.rb
You need to be careful with assumptions regarding the target: The
proxy does not fetch records from the database until it needs them,
but new ones created with +build+ are added to the target. So, the
target may be non-empty and still lack children waiting to be read
from the database. If you look directly to the database you cannot
assume that's the entire collection because new records may have been
added to the target, etc.
A quick check on my console confirmed there the children aren't loaded.
parent.children << child; nil
=> only parent and child where loaded....

Rails difference in object created from a .find(:id) and .where() methods

What is the difference in the objects created with these 2 methods:
tec = Technique.find(6)
tec2 = Technique.where(:korean => 'Jok Sul')
The data returned for each is exactly the same, yet the first object will respond perfectly to an inherited method like update_attributes while the second object will give an error of method not found.
When I do tec.class and tec2.class one is an ActiveRecord::Relation and the other doesn't give me a class at all, it just prints out the content of the object.
Maybe when you use the .where method you get an array, even if there is only one match and therefore you always have to issue the .each method to get at the contents? But that makes it hard to deal with when you want to update records, etc.
Can someone clarify this for me? Specifically, how to deal with matches found through the .where method.
Thanks.
Try:
tec2 = Technique.where(:korean => 'Jok Sul').first
Good question.
tec_scope = Technique.where(:korean => 'Jok Sul') # create an internal query
Remember, here only the query is created, it is not executed. You can programmatically build on top of this query if you so wished. The scope (or query if you so wish) will be executed in 2 ways. "Implicit" or "Explicit". Implicit way of running the query happens for example in the console, which invokes a method on the scope which automatically runs the query for you. This wont happen in your controllers unless you run it explicitly for .e.g
tec_scope.all # returns array
tec_scope.first # retuns one element
Scopes are just adding where clauses/predicates to your query. It's query building and delaying the execution till it is needed.
However,
tec_objects = Technique.find(6) # explicitly runs a query and returns one object (in this case)
This will explicitly run the query there and then. It is a question of the timing of execution of the query.
The difference is subtle but very important.
This hasnt got anything to do with whether you get one result or an array.
Technique.find([4,5]) # will return an array
Technique.find(4) # will return one object
Technique.where(:some_key => "some value").all # will return an array
Technique.where(:id => 5).first # will return one object
The difference is in timing of the execution of the query. Don't let the console fool you into believing there is no difference. Console is implicitly firing the query for you :)
The find(6) returns a single object, because you're specifying the object ID in the database, which is guaranteed to be unique by convention.
The where call returns a collection, which may be only 1 item long, but it still returns a collection, not a single object.
You can reveal this difference. Using your example code, if you call tec.class vs. tec2.class I think you'll find that they aren't the same class of object, as you expect.
That is, the methods available to a collection of objects is different than the methods available on an instance of that object.

Resources