Can I get load attributes from a one-to-one relationship for delegation without instantiating a related model?
Imagine the following:
class House < ActiveRecord::Base
belongs_to :ground
delegate :elevation_in_meters, to: :ground
# attributes: stories, roof_type
end
class Ground < ActiveRecord::Base
has_one :house
# attributes: elevation_in_meters, geo_data
end
Then, to handle the load so that house.elevation_in_meters
it can be called without loading Ground
, I can do:
houses=House.includes(:ground).first(3)
The problem is that the whole object is Ground
actually created with all the attributes, including the attribute geo_data
, which I don't need in this case. The reason I don't care is because the request needs to be VERY perfect and geo_data
- a fairly large textbox. I only need to read delegated attributes, not write to them.
What approach could I look to load an attribute elevation_in_meters
from Ground
without loading everything from Ground
?
I'm on the rails 4.1 btw
NOTE. It is desirable that this wish is loaded by default for House
, so I don't have to specify it every time.
source to share
First, write the area for the model you want to partially get and select the fields you need. Note that I used the full name (with the table name) and a string to select. I'm not sure if you could just select(:elevation_in_meters,:geo_data)
as I copied this from our production example and we are using some joins with this scope that won't work without the table name. Try it yourself.
class Ground < ActiveRecord::Base
has_one :house
attributes: elevation_in_meters, geo_data
scope :reduced, -> {
select('grounds.elevation_in_meters, grounds.geo_data')
}
end
If you have a scope, you can do a second belongs_to relationship (don't be afraid it messes up your first, since rails relationships are basically just methods you create) that calls the scope on your Ground
model.
class House < ActiveRecord::Base
belongs_to :ground
belongs_to :ground_reduced, ->(_o) { reduced },
class_name: 'Ground', foreign_key: 'ground_id'
delegate :elevation_in_meters, to: :ground_reduced
# go for an additional delegation if
# you also need this with the full object sometimes
end
In the end, you can simply call your request like this:
houses = House.includes(:ground_reduced).first(3)
Technically this is not the correct answer to your question as the object Ground
is still being instantiated . But the instance will only have the data you need and the rest of the fields will be null, so it should do the trick.
UPDATE: Since I only saw that you want this behavior to be the default, just add an area for your home:
scope :reduced, -> { includes(:ground_reduced) }
You can then add this as your default scope, since your original relationship will not be affected by this.
source to share
I know it has been a while, but I just stumbled upon this.
If you are only interested in the singular attribute, you can also use joins
in combination with select
, and the attribute will magically be added to your instance of your house.
res = House.joins(:ground).select('houses.*, grounds.elevation_in_meters').first
res.elevation_in_meters # attribute is available on the object
To always have this attribute present, make it default_scope
for House
, for example:
default_scope { joins(:ground).select('houses.*, grounds.elevation_in_meters') }
Depending on the nature of the tables you are joining, you may need one as well distinct
.
source to share