Ruby on Rails, Model Joins - ruby-on-rails

I have 2 models:
class Video < ActiveRecord::Base
belongs_to :categories, :foreign_key => "category", :class_name => "Category"
end
class Category < ActiveRecord::Base
has_many :videos
end
This is fine so far, in my videos controller for the index page I have:
def index
#videos = Video.all(:joins => :categories)
etc, etc
end
The above produces the following SQL Query: SELECT videos.* FROM videos INNER JOIN categories ON categories.id = videos.category
Which is fine up to a certain point, basically I need to get the category name (a field in that table) that way I don't have to do another call in the view to get category name based on the category id. Any ideas?
Thank you, and yes I am new to ruby, I tried reading the API but couldn't find much help there.

If think the association in your Video class is set up incorrectly. It should be:
class Video < ActiveRecord::Base
belongs_to :category
end
You can do #videos = Video.all(:include => :category). This will retrieve the video and associated category records using a single SQL statement.
Incidentally, your :class_name option on the belongs_to association is redundant because ActiveRecord will already have automatically inferred the class name from the association name. You should only use this option if you want to have an association name that is different from the underlying class e.g. authors/Person.

join_condition = " ,categories where INNER JOIN categories ON categories.id=videos.category"
#videos = Video.all(:joins => join_condition)
try this

Related

Setting a default order for a model's association

Say I have 2 models, Category and Article; given the following association:
Category has_many :articles.. and Article belongs_to :category
If I do.. Article.all I get an array back in ASC order.
Now, Rails allows me to query a Category's Articles with: Category.find(:id).articles... but in doing so I get an array back in DESC order.. is there a way to override the default behavior of this so that I can order this array using x column in the Article's table without having to chain .order('column_name') everywhere I do this?
Hope this makes sense, thanks.
You could specify the order when define association.
class Category < ActiveRecord::Base
has_many :articles, :order => "updated_at DESC"
end
For Article.all's order, you could define the default_scope
class Article < ActiveRecord::Base
default_scope { order('updated_at DESC') }
end

Retrieve data from join table

I am new in RoR and I am trying to write a query on a join table that retrieve all the data I need
class User < ActiveRecord::Base
has_many :forms, :through => :user_forms
end
class Form < ActiveRecord::Base
has_many :users, :through => :user_forms
end
In my controller I can successfully retrieve all the forms of a user like this :
User.find(params[:u]).forms
Which gives me all the Form objects
But, I would like to add a new column in my join table (user_forms) that tells the status of the form (close, already filled, etc).
Is it possible to modify my query so that it can also retrieve columns from the user_forms table ?
it is possible. Once you've added the status column to user_forms, try the following
>> user = User.first
>> closed_forms = user.forms.where(user_forms: { status: 'closed' })
Take note that you don't need to add a joins because that's taken care of when you called user.forms.
UPDATE: to add an attribute from the user_forms table to the forms, try the following
>> closed_forms = user.forms.select('forms.*, user_forms.status as status')
>> closed_forms.first.status # should return the status of the form that is in the user_forms table
It is possible to do this using find_by_sql and literal sql. I do not know of a way to properly chain together rails query methods to create the same query, however.
But here's a modified example that I put together for a friend previously:
#user = User.find(params[:u])
#forms = #user.forms.find_by_sql("SELECT forms.*, user_forms.status as status FROM forms INNER JOIN user_forms ON forms.id = user_forms.form_id WHERE (user_forms.user_id = #{#user.id});")
And then you'll be able to do
#forms.first.status
and it'll act like status is just an attribute of the Form model.
First, I think you made a mistake.
When you have 2 models having has_many relations, you should set an has_and_belongs_to_many relation.
In most cases, 2 models are joined by
has_many - belongs_to
has_one - belongs_to
has_and_belongs_to_many - has_and_belongs_to_many
has_and_belongs_to_many is one of the solutions. But, if you choose it, you must create a join table named forms_users. Choose an has_and_belongs_to_many implies you can not set a status on the join table.
For it, you have to add a join table, with a form_id, a user_id and a status.
class User < ActiveRecord::Base
has_many :user_forms
has_many :forms, :through => :user_forms
end
class UserForm < ActiveRecord::Base
belongs_to :user
belongs_to :form
end
class Form < ActiveRecord::Base
has_many :user_forms
has_many :users, :through => :user_forms
end
Then, you can get
User.find(params[:u]).user_forms
Or
UserForm.find(:all,
:conditions => ["user_forms.user_id = ? AND user_forms.status = ?",
params[:u],
'close'
)
)
Given that status is really a property of Form, you probably want to add the status to the Forms table rather than the join table.
Then when you retrieve forms using your query, they will already have the status information retrieved with them i.e.
User.find(params[:u]).forms.each{ |form| puts form.status }
Additionally, if you wanted to find all the forms for a given user with a particular status, you can use queries like:
User.find(params[:u]).forms.where(status: 'closed')

3 or more model association confusion at Rails

It has been almost a week since I'm trying to find a solution to my confusion... Here it is:
I have a Program model.
I have a ProgramCategory model.
I have a ProgramSubcategory model.
Let's make it more clear:
ProgramCategory ======> Shows, Movies,
ProgramSubcategory ===> Featured Shows, Action Movies
Program ==============> Lost, Dexter, Game of Thrones etc...
I want to able to associate each of these models with eachother. I've got what I want to do particularly with many-to-many association. I have a categories_navigation JOIN model/table and all of my other tables are connected to it. By this way, I can access all fields of all of these models.
BUT...
As you know, has_many :through style associations are always plural. There is nothing such as has_one :through or belongs_to through. BUT I want to play with SINGULAR objects, NOT arrays. A Program has ONLY ONE Subcategory and ONLY ONE Category. I'm just using a join table to only make connection between those 3. For example, at the moment I can access program.program_categories[0].title but I want to access it such like program.program_category for example.
How can I have 'has_many :through's abilities but has_one's singular usage convention all together? :|
P.S: My previous question was about this situation too, but I decided to start from scratch and learn about philosophy of associations. If you want so you may check my previous post here: How to access associated model through another model in Rails?
Why a join table where you have a direct relationship? In the end, a program belongs to a subcategory, which in turn belongs to one category. So no join table needed.
class Program < ActiveRecord::Base
belongs_to :subcategory # references the "subcategory_id" in the table
# belongs_to :category, :through => :subcategory
delegate :category, :to => :subcategory
end
class Subcategory < ActiveRecord::Base
has_many :programs
belongs_to :category # references the "category_id" in the table
end
class Category < ActiveRecord::Base
has_many :subcategories
has_many :programs, :through => :subcategories
end
Another point of view is to make categories a tree, so you don't need an additional model for "level-2" categories, you can add as many levels you want. If you use a tree implementation like "closure_tree" you can also get all subcategories (at any level), all supercategories, etc
In that case you skip the Subcategory model, as it is just a category with depth=2
class Program < ActiveRecord::Base
belongs_to :category # references the "category_id" in the table
scope :in_categories, lambda do |cats|
where(:category_id => cats) # accepts one or an array of either integers or Categories
end
end
class Category < ActiveRecord::Base
acts_as_tree
has_many :programs
end
Just an example on how to use a tree to filter by category. Suppose you have a select box, and you select a category from it. You want to retrieve all the object which correspond to any subcategory thereof, not only the category.
class ProgramsController < ApplicationController
def index
#programs = Program.scoped
if params[:category].present?
category = Category.find(params[:category])
#programs = #programs.in_categories(category.descendant_ids + [category.id])
end
end
end
Tree-win!

Using named scopes in rails 3.0

I have recently been taking a course in Rails. We are tasked with creating three named scopes in our 'Product' model. I have done:
scope :books, where( :category => 'books')
scope :movies, where( :category => 'movies')
scope :music, where( :category => 'music')
When I call these as 'Product.books' or 'Product.movies' from the command line, I am expecting to see a return of all of my products that are books, or movies. All I get is an empty array []. Is the problem in the definition of the scopes (which I assume), or how I am trying to access them?
Your syntax is OK. I tried it by adding some books and movies.It worked fine and displayed books when i run Product.books.
So,your problem is empty database which is resulting in empty array.
Is Category its own model that is related to Product (such as by has_many or has_one)?
If this is the case, you will need to do a joins with the Category
For example, see the following code:
class Product < ActiveRecord::Base
has_one :category
scope :books, joins(:category).where('categories.name' => 'book')
end
class Category < ActiveRecord::Base
belongs_to :product
end
In this simple example, both Product and Category only have name attributes as strings (in the migrations), and Category also has product_id.
Your syntax is correct. But first check your database, may be you don't have data related to the Product.books or Product.movies.

In Rails 3 how can I select items where the items.join_model.id != x?

In my Rails models I have:
class Song < ActiveRecord::Base
has_many :flags
has_many :accounts, :through => :flags
end
class Account < ActiveRecord::Base
has_many :flags
has_many :songs, :through => :flags
end
class Flag < ActiveRecord::Base
belongs_to :song
belongs_to :account
end
I'm looking for a way to create a scope in the Song model that fetches songs that DO NOT have a given account associated with it.
I've tried:
Song.joins(:accounts).where('account_id != ?', #an_account)
but it returns an empty set. This might be because there are songs that have no accounts attached to it? I'm not sure, but really struggling with this one.
Update
The result set I'm looking for includes songs that do not have a given account associated with it. This includes songs that have no flags.
Thanks for looking.
Am I understanding your question correctly - you want Songs that are not associated with a particular account?
Try:
Song.joins(:accounts).where(Account.arel_table[:id].not_eq(#an_account.id))
Answer revised: (in response to clarification in the comments)
You probably want SQL conditions like this:
Song.all(:conditions =>
["songs.id NOT IN (SELECT f.song_id FROM flags f WHERE f.account_id = ?)", #an_account.id]
)
Or in ARel, you could get the same SQL generated like this:
songs = Song.arel_table
flags = Flag.arel_table
Song.where(songs[:id].not_in(
flags.project(:song_id).where(flags[:account_id].eq(#an_account.id))
))
I generally prefer ARel, and I prefer it in this case too.
If your where clause is not a typo, it is incorrect. Code frequently uses == for equality, but sql does not, use a single equals sign as such:
Song.joins(:accounts).where('account_id = ?', #an_account.id)
Edit:
Actually there is a way to use activerecord to do this for you, instead of writing your own bound sql fragments:
Song.joins(:accounts).where(:accounts => {:id => #an_account.id})

Resources