Access an ActiveRelation in a view - ruby

I have two models with the appropriate foreign key created in the people table:
class Person < ActiveRecord::Base
belongs_to :family
class Family < ActiveRecord::Base
has_many :people
If I do the following I get an object - #family_members - as an instance variable and I have no problems:
#family_members = Family.find(1)
I can access the 'child' people table fields easily in my view:
#family_members.people.first_name
However, if I use the arel way with "where" etc. I get an "ActiveRecord::Relation", not a normal object, which leaves me stumped as to how to access the same "first_name" field form the people table like I accessed above:
#family_members = Family.where(:id => 1)
or even
#family_members = Family.joins(:people).where(:id => 1)
(is the "joins" even required??)
I understand that using ".first" will cause the query to run:
#family_members = Family.where(:id => 1).first
But it returns an array, not an object, so if I use in my view:
#family_members.people.first_name
I get a "method 'people' unknown" error.
How can I access the 'first_name' field of the people table like I did with the object created by "find" but using an ActiveRecord relation?
* added information 7/15 ********
To clarify what I am looking for -- here is what I would have written if I were writing SQL instead of Arel:
SELECT f.home_phone, f.address, p.first_name, p.last_name, p.birthday
FROM families f INNER JOIN people p ON p.family.id = f.id WHERE family_id = 1
With that query's results loaded into a result set I could access:
myResultSet("home_phone") -- the home_phone from the families table
myResultSet("address") -- the address from the families table
myResultSet("first_name") -- the first_name from the people table
myResultSet("birthdate") -- the birthdate from the people table
If the two tables in the query have a same-named field I would just use "AS" to request one of the fields by another name.
I have used this kind of query/result set for many years in web apps and I am trying to deduce how to do the same in Rails and ActiveRecord.

#family_members.people.first_name shouldn't ever work so I'm surprised you find it working ... #family_members contains a Family object, #family_members.people is an array of Person objects.
The fact that you're calling it #family_members seems to make me think you're expecting it to be an array of Persons... in which case the correct code would be...
#family_members = Family.find(1).people # finds people in first Family object
If you expect #family_members to contain just the first family member, then...
#family_members = Family.find(1).people.first
If you want an array of first names of all family members, then...
#family_members = Family.find(1).people # finds people in 1st Family object
#family_members.map {|member| member.first_name} # array of first_name
#family_members = Family.find(1) and #family_members = Family.where(:id => 1) are functionally identical.. both retrieve the first Family object in the database in each case may contain zero, one, or multiple people.
Just to be clear, the "1" in all examples above refer to which Family object is retrieved, not which Person in the Family.

Related

ActiveRecord: check whether association exists without loading it?

Suppose I've got ActiveRecord models such that User has_one :photo. In the database, photos has a t.binary column which may hold a lot of data, so I don't want to SELECT that column unless I need to.
I want to do something like:
users.each do |user|
image_tag(user_photo_path) if user.photo.present?
end
However, I don't want to call user.photo.present? because:
Doing so loads the photo association, including SELECT * from photos
Even if it could be made to only SELECT id FROM photos to check existence, it's still an N + 1 query.
What I really want is to load users with a single query which gives each one a property telling me whether it has an associated photo or not.
With ActiveRecord 5, this works:
class User < ActiveRecord::Base
scope :with_photo_id, -> {
left_outer_joins(:photo).select(
"users.*, photos.id AS photo_id"
)
}
end
Then I can call User.with_photo_id and check user.photo_id.present?.
Prior to AR 5, the join would be uglier:
joins(
"LEFT OUTER JOIN photos ON photos.user_id = users.id"
)

Sequel join: ".id" is returning the id of the the other table

I have a Ruby app which uses Ramaze and Sequel.
I have the classes Tag and Tagging which have this relationship:
class Tag < Sequel::Model
one_to_many :taggings, :class => 'Thoth::Tagging'
class Tagging < Sequel::Model(:taggings)
many_to_one :tag, :class => 'Thoth::Tag'
I want to return a list of tags in order of popularity, the ones that have the most taggings (filtering out any that have less than three taggings). I'm doing that this way:
tags = .left_outer_join(:taggings, :tag_id => :id).group(:tag_id).having("count(*) > 2").order("count(*) desc").all
This does return what seem to be tag objects, but when I call .id on them, I get the id of a tagging that points to the tag, rather than the tag itself.
On closer inspection, the results are quite different from a regular find:
> tag_regular = Tag[2]
=> #<Thoth::Tag #values={:title=>nil, :position=>nil, :parent_id=>1, :name=>"academic", :id=>2}>
> tag_from_join = Tag.join(:taggings, :tag_id => :id).group(:tag_id).having("count(*) > 2").order("count(*) desc").all.select{|tag| tag.name == "academic"}.first
=> #<Thoth::Tag #values={:tag_id=>2, :post_id=>5, :title=>nil, :position=>nil, :parent_id=>1, :name=>"academic", :id=>1611, :created_at=>nil}>
In both cases I get a Thoth::Tag, but the values are quite different, based on the different fields in the join I suppose.
All I actually need to do is get a list of regular tag objects sorted by the number of taggings, but in an efficient single-query way. Is there a better way?
The default selection is *, so you are selecting columns from both tags and taggings. If you have an id column in both tables, because Sequel returns records as a hash keyed by column name, columns in the taggings table will override columns with the same name in the tags table.
If you only want the columns from tags, add select_all(:tags) to the dataset.
The Sequel master branch has a table_select plugin that will handle this situation by default.

Timestamp Column from Joined Table Becomes String

I have a table named subs which has many articles. The articles table has a timestamp column called published.
Sub.select( "subs.*,MAX(articles.published) published").joins("LEFT OUTER JOIN articles ON subs.id=articles.sub_id").group("subs.id").first.published.class
=> String
Article.select("max(published) published").group("id").first.published.class
=> ActiveSupport::TimeWithZone
I want to get an ActiveSupport::TimeWithZone object back from the first query.
Rails 3
Rails determines how to type cast attributes based on their database column definitions. For example, say you have a created_at method on your Sub model. When a record is loaded read_attribute is used (ActiveRecord::AttributeMethods::Read). This uses type_cast_attribute which determines how to cast the value based on the column info. For example, if you are using PostgreSQL it may use:
Sub.columns.detect { |c| c.name == "created_at" }.type_cast_code("v")
=> "ActiveRecord::ConnectionAdapters::PostgreSQLColumn.string_to_time(v)"
But Rails doesn't know what to do with columns that aren't on the Sub model. So it just gives back a String. If you need to work with a ActiveSupport::TimeWithZone object, you can cast the value with:
published = Sub.select( "subs.*,MAX(articles.published) published").joins("LEFT OUTER JOIN articles ON subs.id=articles.sub_id").group("subs.id").first.published
published.present? ? Time.zone.parse(published) : nil
Rails 4
In Rails 4, Rails is smarter about this kind of type-casting. When the SQL is executed, ActiveRecord::Result is created and the column_types are passed to the initializer. In your example Sub.select query, the published column would be cast as a Time object.

Ruby, Retrieve Child Object By Key

I am trying to retrieve a child object based on the key in its parent's table. For instance, I have the Customer class which contains a "store_id" key to the Stores tables. If a customer has a "store_id" key, I would like to bring back that Store object and not the parent Customer object.
EDIT: Here is a sql statement showing what I am trying to do.
So the SQL statement would look something like this.
"SELECT storeS.* FROM customers INNER JOIN stores ON customers.store_id = storeS.id WHERE customers.id = '9'"
I know the sql is probably wrong, but thats a very concise way to show it.
I am assuming you are using rails with the out-of-the-box configuration (using ActiveRecord).
By convention, the "store_id" key in the "customers" table should match an "id" field in the "stores" table. You should also have the following class models setup:
class Store < ActiveRecord::Base
has_many :customers # this is not required for what you want to do here, but recommended
end
class Customer < ActiveRecord::Base
belongs_to :store
end
Assuming this is true, you can either do this if you have the store key:
# assuming we have store key == 9
Store.find(key)
Or you could do this if you already have the customer:
# assuming we have customer.store_id == 9
customer.store
Or if you only have the customer key:
# assuming we have a customer key == 9
customer = Customer.find(9)
store = customer.store
I don't use ActiveRecord a lot, but I think it's this:
Store.find(customer.store_id)

ActiveRecord: Find through multiple instances

Say I have the following in my controller:
#category1
#category2
and I want to find all stores associated with those two categories...
#stores = #category1.stores + #category2.stores
this does work, but unfortunately returns an unaltered Array, rather than a AR::Base Array, and as such, I can't do things like pagination, scope, etc...
It seems to me like there's a built-in way of finding through multiple instance association... isn't there?
##stores = #category1.stores + #category2.stores
#if you want to call API methods you can just add conditions with the category id
#stores = Store.find(:all, :conditions => ['category_id=?', a || b])
With ActiveRecord, whenever you're finding a set of unique model objects, calling find on that model is usually your best bet.
Then all you need to do is constrain the join table with the categories you care about.
#stores = Store.all(:joins => :categories,
:conditions => ['category_stores.category_id in (?)', [#category1.id, #category2.id]])

Resources