How does .first work on a CollectionProxy in Rails? - ruby-on-rails

I have a couple models set up like this:
class Contract < ActiveRecord::Base
has_many :invoices, dependent: :destroy
end
class Invoice < ActiveRecord::Base
belongs_to :contract
end
I have a feature test set up like this...
feature "Some cool functionality", js: true do
let(:contract) { create(:contract) }
let(:invoice) { create(:invoice, contract: contract) }
#etc...
end
While debugging the test I noticed this...
(byebug) p contract
#<Contract id: 1, created_at: "2014-02-25 01:52:52", updated_at: "2014-02-25 01:52:52">
(byebug) p invoice
#<Invoice id: 1, contract_id: 1, created_at: "2014-02-25 01:52:52", updated_at: "2014-02-25 01:52:52">
Here's the confusing part:
(byebug) p contract.invoices.first
nil
I thought that would return my invoice defined in my feature test.
However, I think I can verify that contract has one invoice...
(byebug) p contract.invoices.count
(1.0ms) SELECT COUNT(*) FROM "invoices" WHERE "invoices"."contract_id" = $1 [["contract_id", 1]]
1
What's going on here?

Try calling contract.reload
When you call let the value of the variable/method is cached after the first time it is invoked. So when you call contract.invoices.first you are invoking invoices on the cached contract object currently in memory.

To ensure you're using the most up-to-date data, try using Rails' reload! console method:
# from command line
reload!

Related

RSpec has_many through #<ActiveRecord::Associations::CollectionProxy> on after_save

Heres my relationship model
class Address < ApplicationRecord
has_many :address_aliases, :inverse_of => :address, :foreign_key => :address_id
end
In spec I am building an address with address_aliases. In my after_save of address I have used address.address_aliases.pluck somewhere, and it does not give correct value.
address_aliases = FactoryGirl.build_list(:address_alias, 1, :alias_for_city => "TEST1")
address = FactoryGirl.build(:some_address, :company_id => "test_company", :address_aliases => address_aliases)
byebug
expect ...
address.save!
This is what I get on byebug. address.address_aliases has one element, but when I pluck it returns blank array.
(byebug) address.address_aliases
#<ActiveRecord::Associations::CollectionProxy [#<AddressAlias id: nil, alias_for_city: "TEST1", created_at: nil, updated_at: nil, address_id: nil>]>
(byebug) address.address_aliases.pluck(:alias_for_city)
[]
The problem is that the aliases are not yet persisted and pluck does a database query (see when you inspect address_aliases, the record does not have an ID yet, it's on memory, it's not on the database yet).
Replace that pluck with map(&:alias_for_city) so it doesn't do a database query and uses the already loaded collection.

Rails CollectionProxy randomly inserts in wrong order

I'm seeing some weird behaviour in my models, and was hoping someone could shed some light on the issue.
# user model
class User < ActiveRecord::Base
has_many :events
has_and_belongs_to_many :attended_events
def attend(event)
self.attended_events << event
end
end
# helper method in /spec-dir
def attend_events(host, guest)
host.events.each do |event|
guest.attend(event)
end
end
This, for some reason inserts the event with id 2 before the event with id 1, like so:
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 2, name: "dummy-event", user_id: 1>, #<Event id: 1, name: "dummy-event", user_id: 1>
But, when I do something seemlingly random - like for instance change the attend_event method like so:
def attend_event(event)
self.attended_events << event
p self.attended_events # random puts statement
end
It gets inserted in the correct order.
#<ActiveRecord::Associations::CollectionProxy [#<Event id: 1, name: "dummy-event", user_id: 1>, #<Event id: 2, name: "dummy-event", user_id: 1>
What am I not getting here?
Unless you specify an order on the association, associations are unordered when they are retrieved from the database (the generated sql won't have an order clause so the database is free to return things in whatever order it wants)
You can specify an order by doing (rails 4.x upwards)
has_and_belongs_to_many :attended_events, scope: -> {order("something")}
or, on earlier versions
has_and_belongs_to_many :attended_events, :order => "something"
When you've just inserted the object you may see a different object - here you are probably seeing the loaded version of the association, which is just an array (wrapped by the proxy)

When I try to "put" or "<%=" an ActiveRecord (rails) attribute, I get nil, but when I look at the object hash its actually there

So here is an example of a hash for a record of the 'properties' table, the attribute in question being 'owner'
Property.first #=>
#<Property id: 3684, ss_property_id: 1, owner_full_name: "Holliday Associates", owner: "HA",
owners_pctg: 100, tax_map_id: "0460001047", county: "Horry", description: "L.S. Alford", acreage:
131.0, prop_taxes_2009: 180.72, prop_taxes_2010: 173.99, prop_taxes_2011: 172.94, notes: nil,
created_at: "2013-04-03 01:16:23", updated_at: "2013-04-03 01:16:26">
When I do something like this, however
1.9.3p194 :011 > Property.first.owner
Property Load (0.3ms) SELECT "properties".* FROM "properties" LIMIT 1
=> nil
it is nil.
EDIT: here is my model (dramatically over-simplified...)
class Property < ActiveRecord::Base
belongs_to :owner
end
My model ended up having a conflict with the :owner namespace. It actually belongs_to :owner, a new model I haven't even started using yet. The :owner namespace apparently got overwritten by the ActiveRelation method to the Owner model

Restrict eagerly loaded with `where` without another query in Rails

A has many Bs, B has many Cs. C has a property called thing:
class A < ActiveRecord::Base
has_many :bs
end
class B < ActiveRecord::Base
belongs_to :a
has_many :cs
end
class C < ActiveRecord::Base
belongs_to :b
attr_accessible :thing
end
I'd like to query for all Bs belonging to an A, and eagerly load Cs that belong to said B:
> a = A.first
A Load (0.2ms) SELECT "as".* FROM "as" LIMIT 1
=> #<A id: 1, created_at: "2012-08-21 09:25:18", updated_at: "2012-08-21 09:25:18">
> bs = a.bs.includes(:cs)
B Load (0.2ms) SELECT "bs".* FROM "bs" WHERE "bs"."a_id" = 1
C Load (0.1ms) SELECT "cs".* FROM "cs" WHERE "cs"."b_id" IN (1)
=> [#<B id: 1, a_id: 1, created_at: "2012-08-21 09:25:22", updated_at: "2012-08-21 09:25:22", thing: nil>]
>
This works well:
> bs[0]
=> #<B id: 1, a_id: 1, created_at: "2012-08-21 09:25:22", updated_at: "2012-08-21 09:25:22", thing: nil>
> bs[0].cs
=> [#<C id: 1, b_id: 1, thing: 2, created_at: "2012-08-21 09:29:31", updated_at: "2012-08-21 09:29:31">]
>
—but not in the case where I want to later perform where() searches on the Cs that belong to B instances:
> bs[0].cs.where(:thing => 1)
C Load (0.2ms) SELECT "cs".* FROM "cs" WHERE "cs"."b_id" = 1 AND "cs"."thing" = 1
=> []
> bs[0].cs.where(:thing => 2)
C Load (0.2ms) SELECT "cs".* FROM "cs" WHERE "cs"."b_id" = 1 AND "cs"."thing" = 2
=> [#<C id: 1, b_id: 1, thing: 2, created_at: "2012-08-21 09:29:31", updated_at: "2012-08-21 09:29:31">]
>
Note that queries are re-issued, despite our having the available information.
Of course, I can just use Enumerable#select:
> bs[0].cs.select {|c| c.thing == 2}
=> [#<C id: 1, b_id: 1, thing: 2, created_at: "2012-08-21 09:29:31", updated_at: "2012-08-21 09:29:31">]
>
This avoids a re-query, but I was sort of hoping Rails could do something similar itself.
The real downside is that I want to use this code where we don't know if the association has been eagerly loaded or not. If it hasn't, then the select method will load all C for B before doing the filter, whereas the where method would produce SQL to get a smaller set of data.
I'm not convinced this matters at all, but if there was something I'm missing about eager loading, I'd love to hear it.
I don't think you're missing anything. I don't believe active record can do anything that smart -- and it would be very difficult to do reliably I think. Like you say, it would have to determine whether you've eager-loaded the association, but it would also have to make a guess as to whether it would be faster to loop through the in-memory collection of Cs (if it's a small collection) or whether it would be faster to go to the database to get all the appropriate Cs in one shot (if it's a very large collection).
In your case, the best thing might be to just set the default scope to always preload the cs, and maybe even write your own fancy method to get them by thing. Something like this maybe:
class B < ActiveRecord::Base
belongs_to :a
has_many :cs
default_scope includes(:cs)
def cs_by_thing(thing)
cs.select{|c|c.thing == thing}
end
end
Then you could always know that you never go back to the DB when querying for your cs:
a = A.first
[db access]
a.bs.first
[db access]
a.bs.first.cs
a.bs.first.cs_by_thing(1)
a.bs.first.cs_by_thing(2)

Correctness of using methods in model that joins other models in Rails

Maybe the title is confusing, but I didn't know how to explain my doubt.
Say I have the following class methods that will be helpful in order to do chainings to query a model called Player. A Player belongs_to a User, but if I want to fetch Players from a particular village or city, I have to fetch the User model.
def self.by_village(village)
joins(:user).where(:village => "village")
end
def self.by_city(city)
joins(:user).where(:city => "city")
end
Let's say I want to fetch a Player by village but also by city, so I would do...
Player.by_city(city).by_village(village).
This would be doing a join of the User twice, and I don't think that is correct.. Right?
So my question is: What would be the correct way of doing so?
I haven't tried that, but I would judge the answer to your question by the actual sql query ActiveRecord generates. If it does only one join, I would use it as you did, if this results in two joins you could create a method by_village_and_city.
OK. Tried it now:
1.9.2p290 :022 > Player.by_city("Berlin").by_village("Kreuzberg")
Player Load (0.3ms) SELECT "players".* FROM "players" INNER JOIN "users" ON "users"."id" = "players"."user_id" WHERE "users"."city" = 'Berlin' AND "users"."village" = 'Kreuzberg'
=> [#<Player id: 1, user_id: 1, created_at: "2012-07-28 17:05:35", updated_at: "2012-07-28 17:05:35">, #<Player id: 2, user_id: 2, created_at: "2012-07-28 17:08:14", updated_at: "2012-07-28 17:08:14">]
So, ActiveRecors combines the two queries, does the right thing and I would use it, except:
I had to change your implementation though:
class Player < ActiveRecord::Base
belongs_to :user
def self.by_village(village)
joins(:user).where('users.village' => village)
end
def self.by_city(city)
joins(:user).where('users.city' => city)
end
end
and what you're doing is usually handled with parameterized scopes:
class Player < ActiveRecord::Base
belongs_to :user
scope :by_village, lambda { |village| joins(:user).where('users.village = ?', village) }
scope :by_city, lambda { |city| joins(:user).where('users.city = ?', city) }
end

Resources