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.

+3


source to share


2 answers


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.

+1


source


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

.

0


source







All Articles