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
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?
I have a table name Strategy which looks like this:
+---+ +----+ +---+
|id | | a | | b
+---+ +----+ +---+
|1 | | 1 | | 2 |
|2 | | 2 | | 3 |
|3 | | 2 | | 4 |
|4 | | 4 | | 1 |
|5 | | 4 | | 4 |
+---+ +----+ +---+
and I have a class name Plan which looks like:
class Plan < ActiveRecord::Base
attr_accessible :a, :b
has_one :strategy ...................
end
Now I want to fill this empty has-one relation. Strategy table has column a and b and two column will always have different combination as shown in table. Now I dont know how to build a relation ship on between two table because Strategy table doesn't belong to Plan nor Plan table store strategy table id. Strategy table only contains some static values. But Plan has some a and b value and I want the id of Strategy Table on the basis of plan's a and b value using has_one relation. Is it possible in rails to do that or I need to follow some old logic.
You can build an association manually.
in your Plan model
def strategy
#strategy ||= Strategy.where("a = ? AND b = ?", a, b).first
end
this will let you reference my_plan.strategy in your code
EDIT answer updated to include Chandranshu's excellent suggestions... memoization and handling the case of more than one strategy with duplicate a, b values
I have two model hotel and theme and both has has_and_belongs_to_many relationship
and third table name is hotels_themes, So I want to delete record only from third tables hotels_themes.
hotels_themes;
+----------+----------+
| hotel_id | theme_id |
+----------+----------+
| 8 | 4 |
| 9 | 5 |
| 11 | 2 |
| 11 | 4 |
| 11 | 6 |
| 12 | 2 |
| 12 | 5 |
+----------+----------+
I want to delete record which match hotel_id and theme_id.
Like sql query delete from hotels_themes where hotel_id=9 and theme_id=5
Use the method delete added to HABTM collections:
hotel = Hotel.find(hotel_id)
theme = Theme.find(theme_id)
hotel.themes.delete(theme)
You just need to empty out the association on either model instance depending on what you are trying to remove. For example:
hotel.themes = []
# or
theme.hotels = []
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.