How to query across 2 different tables in Rails? - ruby-on-rails

class Answer < ActiveRecord::Base
attr_accessible :question_id, :result, :user_id
end
class Question < ActiveRecord::Base
attr_accessible :prompt, :topic
end
I have the following 2 models above in my Rails app. I'd like to run a query that selects questions that have a certain topic (Math) but that are unanswered for a given user_id (result = "Unanswered").
I'm having trouble coming up with the query that would get this.
first_question = Question.where(:topic => "Math")
But I'm not sure how to incorporate the :result => "Unanswered" from the Answer table. Any advice?

To get all unanswered questions for user #1 under the MATH topic use:
Question.includes(:answers).where(:topic => 'MATH', 'answers.result' => 'Unanswered', 'answers.user_id' => 1)
If you want to just get the first unanswered question:
Question.includes(:answers).where(:topic => 'MATH', 'answers.result' => 'Unanswered', 'answers.user_id' => 1).first
I might create some scopes to make these easier to reuse:
class Question < ActiveRecord::Base
has_many :answers
scope :topic, lambda {|topic|
where(:topic => topic)
}
scope :unanswered_by, lambda { |user|
includes(:answers).where('answers.result' => 'Unanswered', 'answers.user_id' => user.id)
}
end
So you can then query like this:
user = User.last
Question.topic('MATH').unanswered_by(user)

You could use a joins to do an inner join between the questions and answers table, then merge a query for :result => "Unanswered":
Question.joins(:answer).where(:topic => "Math", :user_id => <user_id>).merge(Answer.where(:result => "Unanswered"))

Related

how to use joins to get back a single query selected elements from two tables

I have (in Rails 3.2.13):
class User < ActiveRecord::Base
has_many :app_event_login_logouts
end
class AppEventLoginLogout < ActiveRecord::Base
belongs_to :user
end
and would like to get back something like:
AppEventLoginLogoug.select("id, type, users.email").joins(:user)
basically id, type from app_event_login_logouts and email from users but this doesn't seem to be working. What would be the correct syntax?
Try out following code:
ret = User.joins(:app_event_login_logouts).select('app_event_login_logouts.id, app_event_login_logouts.type, users.email')
ret.first.id # will return app_event_login_logouts.id
ret.first.email # will return users.email
...
AppEventLoginLogoug.find(:all,
{:include => [:users],
:select => ['id', 'type', 'users.email']})
I also checked the apidoc and found something like that:
result= AppEventLoginLogoug.find(:all,
:conditions => ['condition_here'],
:joins => [:users],
:select => 'whatever_to_select'
:order => 'your.order')

Find all Children where Parent and Grandparent meet certain condition?

I have the following model relationships:
class Author
has_many :posts
end
class Post
belongs_to :author
has_many :comments
end
class Comment
belongs_to :post
end
I have boolean columns "active" for Authors and "published" for Posts.
I want to find all comments where author.active: true and post.published: true
Could anyone help me out? I'm able to get all Posts from an Author with author.active: true by using a joins statement (this code in the Post model):
joins(:author).where(authors: {active: true})
but I can't seem to figure out how to get all comments where author.active: true and post.published: true.
This should work
Comment.joins(:post => :author).where("authors.active = true AND posts.published = true" )
or
Comment.joins(:post => :author).where(:post => {:published => true, :author => {:active => true}})

select specific attributes from array rails

I have post model
class Post < ActiveRecord::Base
acts_as_voteable
end
and Vote model
class Vote < ActiveRecord::Base
scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.name]) }
scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.name]) }
scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) }
scope :descending, order("created_at DESC")
belongs_to :voteable, :counter_cache=>true,:polymorphic => true,:touch=>true
belongs_to :voter, :polymorphic => true
attr_accessible :vote, :voter, :voteable
# Comment out the line below to allow multiple votes per user.
validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]
end
when I get the post voters with these method
<% #post.voters_who_voted.each do |voter|%>
<%= voter.name %>
<% end %>
I load my database
how can I select only the user name and user id from these array?
update I changed my code I am using thumbs_up gem I pasted less code first to simplify the question
What do you mean by "load database"? If you want to select only id and name columns, then use #post.users.select([:id, :name]).each ...
Or is it about this problem (according to code that you provided)?
UPD.
voters_who_voted loads all voters and returns array https://github.com/bouchard/thumbs_up/blob/master/lib/acts_as_voteable.rb#L113. You have to add own association to Post model:
has_many :voters, :through => :votes, :source => :voter, :source_type => 'User'
It's just example, perhaps voters will clash with already existing method, if any.
Then use it here instead of voters_who_voted
did you try collect method ??
names = #post.users.collect(&:name)
ids = #post.user.collect(&:id)
If you want it to be related you can make a HASH with it. Id's mapped to the names.

Filtering a relation in Rails

I have this relation in my Product model:
has_many :features, :class_name => 'ProductFeature', :source => :product_feature, :include => :feature
So I can do Product.features
which works fine. But I want to be able to filter that by fields in the feature table, when and if necessary. For example in pseudo code:
find all product features where feature is comparable
compare is a bool field on the feature.
I have been trying for 2 hours solid and cannot figure it out (without writing a new query completely). I can't figure out how to access the feature table's fields from the Product.features relation, as it seems it can only filter on product_features fields.
This is what I have come up with so far:
def features_compare
features.feature.where(:compare => true)
end
But it just says feature is not a valid method, which I understand.
Edit
I have updated my model so the relationships are clearer:
product.rb:
class Product < ActiveRecord::Base
belongs_to :company
belongs_to :insurance_type
has_many :product_features
has_many :reviews
attr_accessible :description, :name, :company
end
product_feature.rb:
class ProductFeature < ActiveRecord::Base
belongs_to :product
belongs_to :feature
delegate :name, :to => :feature
attr_accessible :value
end
feature.rb
class Feature < ActiveRecord::Base
attr_accessible :name, :compare
end
I want to be able to query the product_features that belong to a product and feature where Feature.compare is true. Something like this:
product.rb
def features_compare
product_features.where(:compare => true)
end
This throws an error because compare in in the Feature model, not ProductFeature. I have tried the following in product_feature.rb:
delegate :compare, :to => :feature
but I didn't help.
I will adding a bounty to this in a few hours so please please help me!
find all product features where feature is comparable is just
ProductFeature.joins(:feature).where(:feature => {:compare => true})
You can make that a bit more reusable by introducing a scope:
#in product_feature.rb
scope :with_feature_like, lambda do |filter|
joins(:feature).where(:feature => filter)
end
#elsewhere
ProductFeature.with_feature_like(:compare => true)
#all the product features of a certain product with at comparable features
some_product.product_features.with_feature_like(:compare => true)
Finally, if you want all products with product features with comparable features, you want something like:
Product.joins(:product_features => :feature).where(:feature => {:compare => true})
which of course you can also turn into a scope on Product.
This seems like a has_many :through relationship. Try changing this:
has_many :features, :class_name => 'ProductFeature', :source => :product_feature, :include => :feature
to this:
has_many :product_features
has_many :features, :through => :product_features
As long as your ProductFeature model has this:
belongs_to :product
belongs_to :feature
And you have the appropriate columns on product_features (product_id, feature_id), then you should be able to access that product's features and all the attributes on both Product and ProductFeature.
See here:
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
EDIT: Here's how to filter by feature fields.
Product.joins(:features).where(:features => {:name => "Size"})
#product.each |p| { p.features.where(:comparable => true) } is probably your best bet here, but I'm open to being enlightened.

ActiveRecord find NOT(has_many)

I have a very simple model like this:
class User < ActiveRecord::Base
has_many :cookies
has_many :fortunes, :through => :cookies
end
class Cookie < ActiveRecord::Base
belongs_to :user
belongs_to :fortune
end
class Fortune < ActiveRecord::Base
has_many :cookies
has_many :users, :through => :cookies
end
For a given user, u, I can do
u.fortunes
This will give me all the fortunes associated with this user via Cookies table. What I want to do is get all Fortunes not returned by u.fortunes.
I tried
Fortune.all(:limit => 5, :conditions => {:user => {:id._ne => u.id} })
but that doesn't work :(. I am new to ActiveRecord.
Thanks
try this:
Fortune.limit(5).where("id not in (?)", u.fortunes.map(&:id))
(I tried it on my own tables)
Or try this
Fortune.includes(:cookies).limit(5).where([ 'cookies.user_id != ? OR cookies.user_id IS NULL', u.id ])
Or with the syntax You use
Fortune.all(:include => :cookies, :limit => 5, :conditions => [ 'cookies.user_id != ? OR cookies.user_id IS NULL', u.id ])
The reason to not use include :users is to avoid one extra join.
EDIT:
The other suggestions are shorter, and I think also a little bit quicker when finding (no joins), I only wanted to show how to use associations.
You can do
ids_to_reject = u.fortunes.map(&:id)
Fortune.all(:limit => 5, :conditions => ["id not in (?)", ids_to_reject])
try this
#fortune=Fortune.find(:all).delete_if{|fortune| !fortune.user.nil? }
It will delete the fortunes which are belongs to user, and give us the remaining.

Resources