i have following model setup
class Category < ActiveRecord::Base
has_ancestry :cache_depth => true, :depth_cache_column => :depth
has_many :watches, :dependent => :destroy
has_many :products, :through => :watches
end
class Watch < ActiveRecord::Base
belongs_to :category
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :watch, :counter_cache => true
belongs_to :category
end
I need to find products through categories name. Category have 2 levels deep(tree structure). 1 - level is a make, 2 - serie. For now im build this type of search query with the help of meta_search gem
#products = (Product.search :watch_category_name_contains => params[:search]).all.paginate(:page => params[:page])
This works and return all products with serie_name. But watch table always contain only category_id of 2 level category(serie), and im need to be able to search products through makes(1 level category). How can i build this type of query? Thanks!
Well, i see some upvotes coming on my old question, so i will answer. Im finish with raw sql for makes and series queries. Here it is:
def self.makes_with_products
find_by_sql "
SELECT makes.* FROM categories makes
WHERE ancestry IS NULL AND makes.id IN (
SELECT series.ancestry FROM products p
INNER JOIN watches w ON w.id = p.watch_id
INNER JOIN categories series ON series.id = w.category_id
WHERE series.ancestry = makes.id AND p.active
)
"
end
def series_with_products
find_by_sql "
SELECT series.* FROM categories series
WHERE series.ancestry = '#{id}'
AND (
SELECT COUNT(*) FROM products p
INNER JOIN watches w ON w.id = p.watch_id
WHERE w.category_id = series.id AND p.active
) > 0
"
end
Hope this help someone.
Related
Using Rails 4, and given the following models:
class Draft < ActiveRecord::Base
has_many :drafters
has_many :users, through: :drafters
end
class Drafter < ActiveRecord::Base
belongs_to :draft
belongs_to :user
end
class User < ActiveRecord::Base
has_many :drafters
has_many :drafts, through: :drafters
end
How can I retrieve all Drafts which are not associated with the User instance current_user? That is, all Drafts d for which there is no Drafter belonging to d and current_user.
I have Squeel available if it helps.
Draft.includes(:drafters).where(:drafters => { :draft_id => nil } )
will return all drafts with no drafter at all.
Draft.includes(:drafters).where.not(:drafter => { user_id => current_user.od })
will return all drafts not belonging to current_user.
For more details, look at the difference between outer an inner join.
You can implement it with includes:
Draft.includes(:drafters)
.where('(drafters.user_id <> ? or drafters.user_id is null)', current_user.id})
.references(:drafters)
With Squeel, you can do:
Draft.joins{drafters.outer}.where{(drafters.user_id != current_user.id) | (drafters.user_id.eq nil)}
which will generate:
SELECT "drafts".* FROM "drafts" LEFT OUTER JOIN "drafters" ON "drafters"."draft_id" = "drafts"."id" WHERE ("drafters"."user_id" != 1 OR "drafters"."user_id" IS NULL)
I've found an answer (with squeel) based on the following nested query:
SELECT "drafts"."id" FROM "drafts"
WHERE "drafts"."id" NOT IN
(SELECT "drafters"."draft_id" FROM "drafters"
WHERE (("drafters"."draft_id" = "drafters"."id"
AND "drafters"."user_id" = 2)))
That's convertible to the Rails+squeel query:
Draft.where{ id.not_in(Drafter.select(:draft_id).where{
(draft_id == drafts.id) & (user_id == omit_user_id)})}
I got a typical tag and whatever-object relation: say
class Tag < ActiveRecord::Base
attr_accessible :name
has_many :tagazations
has_many :projects, :through => :tagazations
end
class Tagazation < ActiveRecord::Base
belongs_to :project
belongs_to :tag
validates :tag_id, :uniqueness => { :scope => :project_id }
end
class Project < ActiveRecord::Base
has_many :tagazations
has_many :tags, :through => :tagazations
end
nothing special here: each project is tagged by one or multiple tags.
The app has a feature of search: you can select the certain tags and my app should show you all projects which tagged with ALL mentioned tags. So I got an array of the necessary tag_ids and then got stuck with such easy problem
To do this in one query you'd want to take advantage of the common double not exists SQL query, which essentially does find X for all Y.
In your instance, you might do:
class Project < ActiveRecord::Base
def with_tags(tag_ids)
where("NOT EXISTS (SELECT * FROM tags
WHERE NOT EXISTS (SELECT * FROM tagazations
WHERE tagazations.tag_id = tags.id
AND tagazations.project_id = projects.id)
AND tags.id IN (?))", tag_ids)
end
end
Alternatively, you can use count, group and having, although I suspect the first version is quicker but feel free to benchmark:
def with_tags(tag_ids)
joins(:tags).select('projects.*, count(tags.id) as tag_count')
.where(tags: { id: tag_ids }).group('projects.id')
.having('tag_count = ?', tag_ids.size)
end
This would be one way of doing it, although by no means the most efficient:
class Project < ActiveRecord::Base
has_many :tagazations
has_many :tags, :through => :tagazations
def find_with_all_tags(tag_names)
# First find the tags and join with their respective projects
matching_tags = Tag.includes(:projects).where(:name => tag_names)
# Find the intersection of these lists, using the ruby array intersection operator &
matching_tags.reduce([]) {|result, tag| result & tag.projects}
end
end
There may be a couple of typos in there, but you get the idea
I am trying to create a Redmine plugin built on Ruby on Rails. I have the following query which fetches the sum of time_entries of all the issues during an invoice.
SELECT SUM( t.hours )
FROM time_entries t
JOIN invoices i ON t.project_id = i.project_id
WHERE t.project_id = <current project id----#project.id>
AND i.id = <billing invoice id>
AND t.updated_on > 'i.created_at'
AND t.updated_on < 'i.due_date'
How can I store this query data inside invoices table column called time_spent or retrieve the results in the invoices view which lists all invoices along with the above query by invoice ID
The associations in model I created goes like this
class Invoice < ActiveRecord::Base
set_table_name "invoices"
set_primary_key "invoice_id"
belongs_to :project
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
has_many :time_entries, :class_name => "TimeEntry", :foreign_key => "project_id"
In the Controller I am calling the model as
#invoices = Invoice.find(:all, :joins=>" INNER JOIN time_entries ON time_entries.project_id = invoices.project_id",:conditions => ["invoices.project_id = ? AND updated_on > ? AND updated_on < ?", #project.id, invoices.created_at, invoices.due_date])
I know the controller instance variable is totally messed up.
Somewhere I doing mistake. Can some one please help me with this.
I'm assuming Rails 3+ and MySQL, this should give you your each item in your #invoices collection an accessor named "time_spent" which will have the sum you are looking for. It will not persist this info in the db, but it retrieves it for your view:
Invoice.where("invoices.project_id = ? AND invoices.updated_on > ? AND invoices.updated_on < ?", #project.id, invoices.created_at, invoices.due_date).select("SELECT invoices.*, SUM( time_entries.hours ) as time_spent").joins(:time_entries).group("invoices.id")
I'm guessing at < Rails 3 below:
Invoice.find(:all,
:conditions => ["invoices.project_id = ? AND updated_on > ? AND updated_on < ?", #project.id, invoices.created_at, invoices.due_date],
:select=>"SELECT invoices.*, SUM( time_entries.hours ) as time_spent",
:joins=>[:time_entries],
:group=>"invoices.id")
(hoping I typed this correctly. If not, the gist is to use the "select" and "group" methods to get your result.)
Got it working with the following code.
Invoice.find(:all, :select => 'invoices.*, SUM( time_entries.hours ) as time_spent_on_issues', :joins=>" JOIN time_entries ON time_entries.project_id = invoices.project_id", :conditions => ["invoices.project_id = ? AND time_entries.updated_on > invoices.created_at AND time_entries.updated_on < invoices.due_date", #project.id])
BTW "miked" code helped a lot
I have the following models:
class Event < ActiveRecord::Base
has_many :action_items
end
class ActionItem < ActiveRecord::Base
belongs_to :event
belongs_to :action_item_type
end
class ActionItemType < ActiveRecord::Base
has_many :action_items
end
And what I want to do is, for a given event, find all the action items that have an action item type with a name of "foo" (for example). So I think the SQL would go something like this:
SELECT * FROM action_items a
INNER JOIN action_item_types t
ON a.action_item_type_id = t.id
WHERE a.event_id = 1
AND t.name = "foo"
Can anybody help me translate this into a nice active record query? (Rails 3 - Arel)
Thanks!
Well I think I solved it myself. Here's what I did
e = Event.find(1)
e.action_items.joins(:action_item_type).where("action_item_types.name = ?", "foo")
Ehm, why not define
has_many :action_item_types, :through => :action_items
and refer to
e.action_item_types.where(:name => "foo")
?
or (as long as "name" is a unique column name)
e.action_items.joins(:action_item_type).where(:name => "foo")
I am trying to use update_all through an association, and i am getting mysql errors, anyone know why please?
class Basket < ActiveRecord::Base
has_many :basket_items
has_many :articles, :through => :basket_items
def activate_articles
articles.update_all :active => true
end
end
class BasketItem < ActiveRecord::Base
belongs_to :basket
belongs_to :item
belongs_to :article
end
Mysql::Error: Unknown column 'basket_items.basket_id' in 'where clause': UPDATE `articles` SET `active` = 1 WHERE ((`basket_items`.basket_id = 114))
http://dev.rubyonrails.org/ticket/5353
Looks like there was a problem with n-n associations using has_many :through and using update all. Nothing seems to have been done.
1-n associations do appear to work.
Bug?
dev.rubyonrails moved it's tickets to github's issue tracker. Here is the moved link: https://github.com/rails/rails/issues/522
#nolman posted this help on the ticket
#daicoden and I at #square were pairing on this and we were able to put something together along the lines of:
class Comment
class << self
def trash_all
sql = "UPDATE #{quoted_table_name} "
add_joins!(sql, {})
sql << "SET #{sanitize_sql_for_assignment({:trashed => true})} "
add_conditions!(sql, {})
connection.execute(sql)
end
end
end
Now you can call todolist.comments(:conditions => {:trashed => false}).trash_all
This results in the following SQL:
UPDATE `comments` INNER JOIN `todos` ON `todos`.id = `comments`.todo_id SET `trashed` = 1 WHERE (`comments`.`trashed` = 0 AND `todos`.`todolist_id` = 968316918)
Hope this helps!