Rails Joins and include columns from joins table - ruby-on-rails

I don't understand how to get the columns I want from rails. I have two models - A User and a Profile. A User :has_many Profile (because users can revert back to an earlier version of their profile):
> DESCRIBE users;
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | UNI | NULL | |
| password | varchar(255) | NO | | NULL | |
| last_login | datetime | YES | | NULL | |
+----------------+--------------+------+-----+---------+----------------+
> DESCRIBE profiles;
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | MUL | NULL | |
| first_name | varchar(255) | NO | | NULL | |
| last_name | varchar(255) | NO | | NULL | |
| . . . . . . |
| . . . . . . |
| . . . . . . |
+----------------+--------------+------+-----+---------+----------------+
In SQL, I can run the query:
> SELECT * FROM profiles JOIN users ON profiles.user_id = users.id LIMIT 1;
+----+-----------+----------+---------------------+---------+---------------+-----+
| id | username | password | last_login | user_id | first_name | ... |
+----+-----------+----------+---------------------+---------+---------------+-----+
| 1 | john | ****** | 2010-12-30 18:04:28 | 1 | John | ... |
+----+-----------+----------+---------------------+---------+---------------+-----+
See how I get all the columns for BOTH tables JOINED together? However, when I run this same query in Rails, I don't get all the columns I want - I only get those from Profile:
# in rails console
>> p = Profile.joins(:user).limit(1)
>> [#<Profile ...>]
>> p.first_name
>> NoMethodError: undefined method `first_name' for #<ActiveRecord::Relation:0x102b521d0> from /Library/Ruby/Gems/1.8/gems/activerecord-3.0.1/lib/active_record/relation.rb:373:in `method_missing' from (irb):8
# I do NOT want to do this (AKA I do NOT want to use "includes")
>> p.user
>> NoMethodError: undefined method `user' for #<ActiveRecord::Relation:0x102b521d0> from /Library/Ruby/Gems/1.8/gems/activerecord-3.0.1/lib/active_record/relation.rb:373:in method_missing' from (irb):9
I want to (efficiently) return an object that has all the properties of Profile and User together. I don't want to :include the user because it doesn't make sense. The user should always be part of the most recent profile as if they were fields within the Profile model. How do I accomplish this?
I think the problem has something to do with the fact that the Profile model doesn't have attributes for User...

Use select() to name the columns you want. At least this works in Rails 3.0.9.
Background: my application has a primary table named :rights. I wanted to be able to ascribe a tag and color to a given :right record so I could easily pick it out of an index listing. This doesn't cleanly fit the Rails picture of associated records; most :rights will never be tagged, and the tags are completely arbitrary (user input via tag/edit).
I could try duplicating the tag data in the :right record, but that violates normal form. Or I could try querying :tags for each :right record, but that is a painfully inefficient approach. I want to be able to join the tables.
MySQL console shows:
mysql> describe rights;
+------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
...
| Tagid | int(11) | YES | | NULL | |
+------------+---------------+------+-----+---------+----------------+
mysql> describe tags;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| TagName | varchar(255) | YES | | NULL | |
| TagColor | varchar(255) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
I am going to use TagName and TagColor in views/rights/index.html.erb, so I want the rights controller to include those columns in the #rights object it passes to the view. Since not every :right has a :tag, I want to use an outer join:
#rights = Right.joins("LEFT OUTER JOIN tags ON rights.Tagid = tags.id")
But, as everyone has found, this alone doesn't work: a block reference to TagName produces a server error. However, if I add a select at the end, all is well:
#rights = Right.joins("LEFT OUTER JOIN tags ON rights.Tagid = tags.id").select("rights.*,tags.TagName as TagName,tags.TagColor as TagColor")
Note added 6/7/13: the select clause does not require aliases - this works too:
.select("rights.*,tags.TagName,tags.TagColor")
Now I can reference TagName and TagColor in my view:
<% #rights.each do |right| %>
<tr ALIGN=Left <%=
# color background if this is tagged
" BGCOLOR=#{right.TagColor}" if right.TagColor
%> > ...
<% end %>

I don't think that you can load users and profiles with join in Rails. I think that in earlier versions of Rails ( < 2.1) loading of associated models was done with joins, but it was not efficient. Here you have some explanation and links to other materials.
So even if you explicite say that you want to join it, Rails won't map it to associated models. So if you say Profile.whatever_here it will always be mapped to Profile object.
If you still want to do what you said in question, then you can call custom sql query and process results by yourself:
p = ActiveRecord::Base.connection.execute("SELECT * FROM profiles JOIN users ON profiles.user_id = users.id LIMIT 1")
and get results row by row with:
p.fetch_row
It will already be mappet to an array.
Your errors are because you are calling first_name and user method on AciveRecord::Relation object and it stores an array of Profile objects, not a single object. So
p = Profile.joins(:user).limit(1)
p[0].first_name
shoud work.
Better way to fetch only one record is to call:
p = Profile.joins(:user).first
p.first_name
p.user
But when you call p.user it will query database. To avoid it, you can use include, but if you load only one profile object, it is useless. It will make a difference if you load many profiles at a time and want to inlcude users table.

Try using select("*").joins(:table)
In this case, you would type:
User.select("*").joins(:profile)
Hope that works for you.

After reading these tips I got the joins to all be loaded in one query by reading 3 ways to do eager loading (preloading) in Rails 3 & 4.
I'm using Rails 4 and this worked like a charm for me:
refs = Referral.joins(:job)
.joins(:referee)
.joins(:referrer)
.where("jobs.poster_id= ?", user.contact_id)
.order(created_at: :desc)
.eager_load(:job, :referee, :referrer)
Here were my other attempts.
#first attempt
#refs = Referral.joins(:job)
# .where("jobs.poster_id= ?", user.contact_id)
# .select("referrals.*, jobs.*")
# works, but each column needs to be explicitly referenced to be used later.
# also there are conflicts for columns with the same name like id
#second attempt
#refs = ActiveRecord::Base.connection.exec_query("SELECT jobs.id AS job_id, jobs.*, referrals.id as referral_id, referrals.* FROM referrals INNER JOIN jobs ON job_id = referrals.job_id WHERE (jobs.poster_id=#{user.contact_id});")
# this worked OK, but returned back a funky object, plus the column name
# conflict from the previous method remains an issue.
#third attempt using a view + rails_db_views
#refs = JobReferral.where(:poster_id => user.contact_id)
# this worked well. Unfortunately I couldn't use the SQL statement from above
# instead of jobs.* I had to explicitly alias and name each column.
# Additionally it brought back a ton of duplicate data that I was putting
# into an array when really it is nice to work with ActiveRecord objects.
#eager_load
#refs = Referral.joins(:job)
# .where("jobs.poster_id= ?", user.contact_id)
# .eager_load(:job)
# this was my base attempt that worked before I added in two more joins :)

I have got round this problem by creating a VIEW in the database which is the join, and then referencing that as if it were a normal ActiveRecord table in the code. This is fine for getting data out of the database, but if you need to update it, then you'll need to go back to the base classes that represent the 'real' tables. I have found this method to be handy when doing reports that use biggish tables - you can get the data out all in one hit. I am surprised that this doesn't seem to be built into ActiveRecord, seems an obvious thing to me!
So for you:
IN SQL:
CREATE VIEW User_Profiles
AS
SELECT P.*, U.first_name
FROM Users U
inner join Profiles P on U.id=P.user_id
IN RUBY models file:
class UserProfile < ActiveRecord::Base
self.primary_key = :id
#same dependencies as profiles
end
**HINT... I always forget to set the owner of the view (I use postgres), so it blows up straight away with much cursing and self-recrimination.

Related

Rails validation In attribute table

i am new to ROR.
i am building a classified ads app, i have the following tables in my database:
(some fields have been removed for simplicity)
Table Uers
This table stores all the users.
user_id
name
email
password
Table Ads
This table stores all the ads.
ad_id
users_user_id (FK)
title
desc
cat_id (FK)
created_at
Sample data:
------------------------------------------------------------------------------
| ad_id | users_user_id | title | desc | cat_id | created_at |
------------------------------------------------------------------------------
| 1 | 1 | iphone 4 | brand new | 2 | 30-11-2015 |
------------------------------------------------------------------------------
Table categories
This table stores all the available categories. cat_id in the ads table relates to cat_id in this table.
cat_id
category
parent_cid
Sample data:
-------------------------------------------
|cat_id| category | parent_cid |
-------------------------------------------
|1 | Electronics | NULL |
|2 | Mobile Phone | 1 |
|3 | Apartments | NULL |
|4 | Apartments - Sale | 3 |
-------------------------------------------
Table ads_attribute
This table contains all the available attributes for a particular category. Relates to categories table.
attr_id
cat_id (FK)
attr_label
attr_name
Sample data:
-----------------------------------------------------------
|attr_id | cat_id | attr_label | attr_name |
-----------------------------------------------------------
|1 | 2 | Operating System | Operating_System |
|2 | 2 | Is Touch Screen | Touch_Screen |
|3 | 2 | Manufacturer | Manufacturer |
|4 | 3 | Bedrooms | Bedrooms |
|5 | 3 | Total Area | Area |
|6 | 3 | Posted By | Posted_By |
-----------------------------------------------------------
Table ads_attr_value
This table stores the attribute value for each ad in ads table.
attr_val_id
attr_id (FK)
ad_id
attr_val
Sample data:
---------------------------------------------
|attr_val_id | attr_id | ad_id | attr_val |
---------------------------------------------
|1 | 1 | 1 | Ios 8 |
|2 | 2 | 1 | 1 |
|3 | 3 | 1 | Apple |
---------------------------------------------
What is the best way (the rails way) to validate the data before storing it in the the ads_attr_value table, given the fact that the values would be in select fields and the user can change them easily for example from Ios 8 to "blabla".
I've thought of storing all the possible values for each attribute in a new table and then check if a value sent by the user exist in that table before storing it in the ads_attr_value. what do you think? I am sure that there is a better way.thanks for sharing.
The rails way would probably to define your relationships with ActiveRecord associations : http://guides.rubyonrails.org/association_basics.html.
Therefore you could easily define on your model
class AdsAttrVal < ActiveRecord::Base
belongs_to :ad
validates :ad, presence: true
end
However please keep in mind that rails way to store an id of the table is to name it "id" and not "model_id" like you did ("user_id", "id"). My exemple suppose that the rails way is respected...
You have to specify the validations you want inside <yourModel>.rb (the model file) . For exame if you want to validate if ad_id is a number you should add the numericality parameter in the validates statement, see below:
class AdsAttrValue < ActiveRecord::Base
validates :ad_id, numericality: true
#validate if add_att_value has the permitted values
validate :myCustomValidation
def myCustomValidation
#your logic of validation goes here
#you can access here all the fields from this object recently created
if attr_val == something
#do something
end
end
end
See that validations from rails have an s at the end (validates), and your own written validations do not have (validate).
This validations are executed when creating the object before storing in database in order to see if it complies the validations and not stored it it does not comply. You can add errors in your own validation to let the user know what gone wrong. Go further with this reading of validations in ruby on rails

How to make the association in model so I can get all places of user_places's id in rails console?

I have 2 tables for example:
user_places
----------------
| id | place_id|
----------------
| 1 | 1 |
----------------
| 1 | 5 |
----------------
| 1 | 6 |
----------------
| 2 | 8 |
And a places table
--------------------------------------------
| id | title | description | image_url |
--------------------------------------------
| 1 | 1 | description1 | image1 |
--------------------------------------------
| 2 | 5 | description2 | image2 |
--------------------------------------------
| 3 | 6 | description3 | image3 |
--------------------------------------------
| ...| ... | description4 | image4 |
How to make the association in both models so I can get all places of user_places's id = 1 in rails console?
You'll want to look up foreign_keys, especially pertaining to Rails.
Rails is basically a way for you to interact with a relational database. As such, if you know how to correctly structure a relational DB, you'll be able to better understand Rails' ActiveRecord associations, and how they fit into applications.
--
How to make the association in both models so I can get all places of user_places's id = 1
You'd do this:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :places
end
#app/models/places.rb
class Place < ActiveRecord::Base
has_and_belongs_to_many :users
end
This means you have to change your user_places table to the following:
#places_users
#user_id | place_id
This will allow you to call:
$ #user = User.find "1"
$ #user.places #-> 1,5,6
--
The other answer recommended a has_many :through relationship. Whilst this allows you to add other data into your join model, it means you have to include a user_places model for no real reason (at this stage).
I would recommend using has_and_belongs_to_many for the moment. This limits you to only having references in your join table, but makes the entire association much simpler:
The big caveat here is that you'll need to change your user_places table to have an alphabetical nameflow (places_users), and make sure you only have the two foreign_keys as columns: user_id | place_id

Get model id where(value==x)

I have what I have to imagine is a profoundly simple question but I haven't been able to find the answer after quite a bit of Googling.
Say I have a user and each user has_one widget. Now the widget table is set up as follows:
mysql> show columns in widgets;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| user_id | int(11) | YES | MUL | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
I want to look up the widget's id by using the user_id and store it as #id in my controller.
I've tried several variations of the following without success:
#widget=Widget.where(:user_id => current_user)
#id=#widget.id
I sort of feel embarrassed asking something this simple but I really couldn't find a straight answer. Many thanks in advance.
Widget.where(:user_id => current_user.id) returns a collection of all widgets belonging to user - it does not take in account the has_one association defined on model, because DB could contain other records for one reason or another.
What you need is the first matching record - Widget.where(:user_id => current_user.id).first. Or simply current_user.widget, to use the association.
If you just one the current user's widget you can use the following:
#widget = current_user.widget
This will use the has_one relationship that you defined.
For a different user:
#widget = some_other_user.widget
You can use the normal ActiveRecord methods in case you jut have the user_id. In that case you can just save the query that gets the user:
#widget = Widget.where(user_id: some_user_id).first
In all the cases you have to be careful, because in both cases #widget can be nil when nothing is found in the database.
You are slight correct, but think this is the way:
#id = Widget.where(user_id: current_user.id).first.try(:id)
So you have to select a widget for specified user_id, then selects first value (if any), and returns id or nil.
In where query always gives array
#widget=Widget.where(:user_id => current_user).first
#widget.id
You can directly use association between user and widget to find widget id, like this:
current_user.widget.try(:id)
try is required for nil value if the current user don't have any widget then current_user.widget will return nil.

ruby on rails - how to take only take one row

Restaurant Load (1.5ms) SELECT * FROM "restaurants" INNER JOIN
"restaurant_branches" ON "restaurant_branches"."restaurant_id" =
"restaurants"."restaurant_id"
+----------+---------+---------+---------+----------+---------+----------+---------+---------+---------+---------+---------+---------+---------+---------+-------+
| resta... | res_... | res_... | crea... | updat... | user_id | resta... | addr...
| addr... | addr... | addr... | addr... | addr... | numb... | numb... | email |
+----------+---------+---------+---------+----------+---------+----------+---------+---------+---------+---------+---------+---------+---------+---------+-------+
| 27 | DOGG... | WE S... | 2014... | 2014-... | 4 | 28 | 405 ...
| | CHICAGO | IL | 60666 | USA | | | |
| 27 | DOGG... | WE S... | 2014... | 2014-... | 4 | 29 | 111 ...
| | CHICAGO | IL | 60661 | USA | | | |
+----------+---------+---------+---------+----------+---------+----------+---------+---------+---------+---------+---------+---------+---------+---------+-------+
As you can see, I have two records for record 27. This is from a joined
table between restaurants and restaurant_branches. How would I approach
this in a view so that when I select a record on my index.html.erb file
when it gets routed to the show.html.erb file it'll only take one row of
that record and only show me one branch instead of 2?
Thank you for any help.
The two records are different, so keep them separate and list both. It's difficult to say which fields are different from the question. Based on the query, I'm assuming they are restaurant_branches fields that are different.
If it's the restaurants that you want to show/edit then use just Restaurant.all however you want to limit. If however you want to edit the restaurant_branches then introduce a RestaurantBranchesController and have a link for the branches separate.
Something like (in erb):
<%= restaurant.id %>
<% restaurant.restaurant_branches.each do |rb| %>
<%= link_to 'Some branch', rb %><br />
<% end -%>
Then the link_to(rb) should link you to RestaurantBranchesController#show action for the particular restaurant_branch.
Collection
Further to what #vee was suggesting, it seems to me that you're calling a collection of data from ActiveRecord. A collection basically means you're going to receive a multitude of objects back, rather than just one record
I'd imagine you have your models set up as follows:
#app/models/restaurant.rb
Class Restaurant < ActiveRecord::Base
has_many :branches, class_name: "RestaurantBranch"
end
#app/models/restaurant_branch.rb
Class RestaurantBranch < ActiveRecord::Base
belongs_to :restaurant
end
This will give you the ability to call the following:
#restaurant = Restaurant.find params[:id]
#restaurant.branches #-> returns the collection
--
Limit
The way to fix this will depend on what you're trying to show.
Typically, you can use something like limit to retrieve a single record, like so:
#restaurant.branches.limit(1)
Like most things in life, this will be much simpler to resolve if you define what you want to limit the results for - what's the purpose?

Does it make sense to convert DB-ish queries into Rails ActiveRecord Model lingo?

mysql> desc categories;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(80) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
mysql> desc expenses;
+-------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| created_at | datetime | NO | | NULL | |
| description | varchar(100) | NO | | NULL | |
| amount | decimal(10,2) | NO | | NULL | |
| category_id | int(11) | NO | MUL | 1 | |
+-------------+---------------+------+-----+---------+----------------+
Now I need the top N categories like this...
Expense.find_by_sql("SELECT categories.name, sum(amount) as total_amount
from expenses
join categories on category_id = categories.id
group by category_id
order by total_amount desc")
But this is nagging at my Rails conscience.. it seems that it may be possible to achieve the same thing via Expense.find and supplying options like :group, :joins..
Can someone translate this query into ActiveRecord Model speak ?
Is it worth it... Personally i find the SQL more readable and gets my job done faster.. maybe coz I'm still learning Rails. Any advantages with not embedding SQL in source code (apart from not being able to change DB vendors..sql flavor, etc.)?
Seems like find_by_sql doesn't have the bind variable provision like find. What is the workaround? e.g. if i want to limit the number of records to a user-specified limit.
Expense.find(:all,
:select => "categories.name name, sum(amount) total_amount",
:joins => "categories on category_id = categories.id",
:group => "category_id",
:order => "total_amount desc")
Hope that helps!
Seems like find_by_sql doesn't have the bind variable provision like find.
It sure does. (from the Rails docs)
# You can use the same string replacement techniques as you can with ActiveRecord#find
Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
Well this is the code that finally worked for me.. (Francois.. the resulting sql stmt was missing the join keyword)
def Expense.get_top_n_categories options={}
#sQuery = "SELECT categories.name, sum(amount) as total_amount
# from expenses
# join categories on category_id = categories.id
# group by category_id
# order by total_amount desc";
#sQuery += " limit #{options[:limit].to_i}" if !options[:limit].nil?
#Expense.find_by_sql(sQuery)
query_options = {:select => "categories.name name, sum(amount) total_amount",
:joins => "inner join categories on category_id = categories.id",
:group => "category_id",
:order => "total_amount desc"}
query_options[:limit] = options[:limit].to_i if !options[:limit].nil?
Expense.find(:all, query_options)
end
find_by_sql does have rails bind variable... I don't know how I overlooked that.
Finally is the above use of user-specified a potential entry point for sql-injection or does the to_i method call prevent that?
Thanks for all the help. I'm grateful.

Resources