So, Members have many subscriptions. A member is canceled if the last subscription canceled_at is not nil. But I can't seem to get the query to work right, to find all members with presently canceled subscriptions.
I'm doing this in the member model
scope :canceled, includes(:subscriptions).
where('subscriptions.canceled_at IS NOT NULL')
but it returns for users who have previously canceled subscriptions too. I need to use limit or something like that, I have the relation defined as
has_many :subscriptions, :order => "subscriptions.created_at DESC", :dependent => :destroy
so i can just work offf the .first, but not sure how to apply that logic to the class scope like that.
How about adding another association
Class Member < ActiveRecord::Base
has_one :last_subscription, :class_name => "Subscription", :foreign_key => "member_id", :order => "created_at desc"
Then try
scope :canceled, includes(:last_subscription).where('subscriptions.canceled_at IS NOT NULL')
Related
I have a user friendship model that I want to write a lookup for
is a habtm relationship called peers, relating 2 users together. A relationship is a single connection (Joe <-> Steve, not Joe -> Steve and Steve -> Joe).
My join table is as follows:
user_id
peer_id
Both store a user id in the relationship. Below is the HABTM on the user.
has_and_belongs_to_many :peers, class_name: 'User',
foreign_key: 'user_id', association_foreign_key: 'peer_id',
join_table: 'users_peers'
I am trying to figure out the finder sql that will allow this record in the join table to show both sides.
user_id = steve.id
peer_id = joe.id
to show the relationships when I call joe.peers and steve.peers. Currently, steve.peers returns joe, but joe.peers shows nothing.
Generally relationships are best expressed as one way, or a pair of one-way relationships. This is because in most relational databases, it's easy to establish an A to B or B to A relationship, but not both with one record. You basically need two queries unless you make a lot of assumptions and hack around.
In my experience, using has_and_belongs_to_many isn't going to make your life easier as it's a relic from Rails 1.0 that isn't nearly as good as the has_many :through method that replaced it. In your case this is how that would play out:
class Node < ActiveRecord::Base
has_many :peers_of,
:class_name => 'Peer',
:foreign_key => :of_user_id
has_many :peers_to,
:class_name => 'Peer',
:foreign_key => :to_user_id
has_many :peers,
:through => :peers_of,
:source => :to_user
has_many :peers_with,
:through => :peers_to,
:source => :of_user
end
class Peer < ActiveRecord::Base
belongs_to :of_user,
:class_name => 'User'
belongs_to :to_user,
:class_name => 'User'
end
The semantics get a little messy, so you'll probably want to adjust them. The idea here is to establish a bi-directional relationship when adding a "peer", where that consists of a pair of A->B and B->A records.
For the purposes of querying you would only fetch, for instance, #user.peers and not have to worry about the peers_with inverse relationship as that should produce identical results if you've maintained data integrity.
You could just write the sql by hand:
class PeerRelation
belongs_to :user1, :class_name=>"User"
belongs_to :user2, :class_name=>"User"
end
class User
def set_peer(user)
user1_id, user2_id = [self.id, user.id].sort
PeerRelation.find_or_create_by_user1_id_and_user2_id(user1_id, user2_id)
end
def peers
User.joins("inner join peer_relations on
peer_relations.user1_id = #{self.id} or
peer_relations.user2_id = #{self.id}")
end
end
But tadman's approach is smarter from a data-integrity perspective, and is more in line with what a DBA would tell you. (see my comment to your question)
I have a number of models in a Rails project that are linked to a user, and I'm running into a loading problem I try to get all of the user's data displayed on a page.
My model declarations look sort of like this:
class User < ActiveRecord::Base
validates_presence_of :server_user_id
has_many :setup_notes, :foreign_key => "server_user_id"
has_many :closure_votes, :foreign_key => "user_id"
end
class SetupNote < ActiveRecord::Base
belongs_to :user, :foreign_key => "server_user_id"
end
I should note that in the SQL table for closure votes, user_id is the same value as server_user_id in the table for users.
When I try using the :include symbol in a Rails query, it ends up using user.id for the search value when I need to find closure votes and setup notes through user.server_user_id. In other words,
me = User.first(:conditions => ["server_user_id = ?", 12610], :include => :setup_notes)
generates the MySQL query
SELECT `setup_notes`.* FROM `setup_notes` WHERE (`setup_notes`.server_user_id = 1)
Which returns an empty set. Is there any way to format the Rails query/models to send the relevant SQL query, or do I need to write a raw query for all of the models associated with the users (and if that's the case, how do I do that?)
Thanks!
If I understand correctly (that User has a server_user_id field), in the User model you have to set the :primary_key option like so:
class User < ActiveRecord::Base
validates_presence_of :server_user_id
has_many :setup_notes, :foreign_key => "server_user_id", :primary_key => "server_user_id"
has_many :closure_votes, :foreign_key => "user_id", :primary_key => "server_user_id"
end
I'm trying to implement Ryan Bates' sortable table columns code (Railscast #228) but I'd like to be able to sort on an associated column. In particular, I have the following models and associations:
class Project < ActiveRecord::Base
belongs_to :program_manager, :class_name => "User"
class User < ActiveRecord::Base
has_many :program_manager_projects, :class_name => "Project", :foreign_key => "program_manager_id"
The association between the Project model and the User model is mediated by the 'program_manager_id' foreign key, which the user sets in the new/edit views using a collection-select dropdown. Here's part of the annotation at the top of project.rb:
# Table name: projects
# program_manager_id :integer
I want to be able to sort my list of projects in the index view by the program manager's name, i.e., by project.program_manager.name.
Ideally, I'd be able to point :order to this name somehow, perhaps with something like this in the index method of my ProjectsController:
#projects = Project.find(:all, :order => project.program_manager.name)
But that obviously won't work (not to mention Ryan's routine implements this with a specific reference to table names from the model to be sorted.)
I've come across some intimidating approaches that use named_scope, such as:
named_scope :most_active, :select => "questions.*", :joins => "left join comments as comments_for_count on comments_for_count.question.id = questions.id", :group => "questions.id", :order => "count(questions.id) desc"
But given my lack of MySQL expertise, this is fairly impenetrable to me.
Can anyone help me either generalize the named_scope example above for my specific case, or point me to a more straightforward strategy?
Thanks very much,
Dean
Let's dissect that named scope you referenced above. Imagine a model Question which has many Comments.
named_scope :most_active, :select => "questions.*", :joins => "left join comments as comments_for_count on comments_for_count.question.id = questions.id", :group => "questions.id", :order => "count(questions.id) desc"
:most_active
the name of your scope. You would reference thusly: Question.find(:all).most_active
:select => "questions.*"
by default scopes selects all columns from your table anyway, so this limits the results to only the questions table, and not the comments table. This is optional.
:joins => "left join comments as comments_for_count on comments_for_count.question.id = questions.id"
this is saying for every question, I also want to get all comments associated with them. The comments table has a column 'question_id' which is what we'll be using to match them up to the appropriate question record. This is important. It allows us access to fields that are not on our model!
:group => "questions.id"
This is required for the count() function in the order clause to tell us that we want the count of comments based on question. We don't need the count function in our order clause, so we also don't need this group statement
:order => "count(questions.id) desc"
Return the results in order of number of comments, highest to lowest.
So for our example, discarding what we don't need, and applying to your needs, we end up with:
:named_scope :by_program_manager_name, :joins => "left join users on projects.program_manager_id = users.id", :order => "users.name"
This named_scope would be called thusly:
Project.find(:all).by_program_manager_name
Note this is basically equivalent to:
Project.find(:all, :joins => "left join users on projects.program_manager_id = users.id", :order => "users.name")
But, as cam referenced above, you should really know the underlying SQL. Your abilities will be severely hampered without this understanding
Okay, I'm thoroughly stumped on this one. I'm trying to build a menu of published web pages organized by category.
Category.rb:
belongs_to :parent, :class_name => "Category", :foreign_key => "parent_id"
has_many :children, :class_name => "Category", :foreign_key => "parent_id"
has_many :pages, :documents, :galleries
Page.rb
belongs_to :category
The Page model also has :is_published, so I'm trying to filter on that as well. I am reluctant to post my feeble query attempts, but see no other solution than to beg much smarter people:
(self is #current_website)
self.categories.includes(:children, :pages).where('pages.is_published = 1')
This returns mostly what I need, but not Parent Categories without published pages. For instance, it works great if I have:
Parent Category
- Published Page
- Child Category
-- Published Page
Where it fails is when I have no published pages in the parent, like this:
Parent Category
- Child Category
-- Published Page
- Child Category
-- Published Page
Thanks in advance for any help on this. I'm trying to learn as much as I can about queries, but I'm against the wall on this.
UPDATE: Implementing KandadaBoggu's suggestion has yielded much better results, this was added to Category.rb
has_many :published_pages, :class_name => "Page",
:conditions => {:is_published => true}
However, when using the following:
self.categories.where(:parent_id => nil).includes({:children => :published_pages},
:published_pages)
I get the results I need, but I also get empty Parent Categories (no published_pages, no child categories with published pages. An example:
- Parent Category
-- Published Page
- Parent Category
-- NOTHING
My temporary fix was to appended the query with:
reject{|category| category.pages.empty? && category.children.empty?}
Thanks again for your help.
Add a new association called published_pages (apart from your current associations)
class Category
has_many :children, :class_name => "Category",
:foreign_key => "parent_id"
has_many :published_pages, :class_name => "Page",
:conditions => { :is_published => true }
end
Now you can get all the categories as follows:
self.categories.includes(:children, :published_pages)
If you are interested in learning why your approach didnt work, read the Rails documentation (scroll 10-15 lines after the Eager loading of associations section). I have included the relevant snippet below:
For example
Post.includes([:author, :comments]).where(['comments.approved = ?', true]).all
This will result in a single SQL query with joins along the lines of:
LEFT OUTER JOIN comments ON comments.post_id = posts.id and
LEFT OUTER JOIN authors ON authors.id = posts.author_id.
Note that using conditions like this can have unintended consequences.
In the above example posts with notion approved comments are not returned
at all, because the conditions apply to the SQL statement as a whole
and not just to the association. You must disambiguate column
references for this fallback to happen, for example :order =>
"author.name DESC" will work but :order => "name DESC" will not.
To eager load filtered rows of an association, use an association with conditions:
class Post < ActiveRecord::Base
has_many :approved_comments, :class_name => 'Comment',
:conditions => ['approved = ?', true]
end
Post.find(:all, :include => :approved_comments)
Consider the following models..
class Product < ActiveRecord::Base
has_many :pricings
end
class Pricing < ActiveRecord::Base
belongs_to :server
end
Pricing is a historical tracking table for prices of products, so there may potentially be hundreds of price points captured over time. What I want to add is a way to get only the current pricing for the product.
While i can add a has_one relation to the model like the following:
has_one :current_pricing, :class_name => "Pricing", :foreign_key => "product_id",
:order => 'created_at DESC'
This will fetch me all the Pricings for the current product before returning me only the first record.
I am looking for a way to accomplish this in a more efficient manner and was hoping that someone would have input or previous experience doing something similar.
You could use a named scope. Put this at the top of the Pricing model:
class Pricing < ActiveRecord::Base
named_scope :current, lambda { |product| { :conditions => { :product_id => product.id }, :order => 'created_at DESC', :limit => 1 } }
end
Then to get the current pricing of a product, you'd call "Pricing.current(product).first".
In addition to the named scope on pricing, you could add an accessor method to the Product model like so:
class Product < ActiveRecord::Base
def current_pricing
Pricing.current(self).first
end
end
You're looking for a named_scope.Define this in your Pricing model.Supply it with 1 parameter, the product_id.
named_scope :latest_price, lambda { |*args| {:conditions => {:product_id => args.first}, :order => "created_at DESC", :limit => 1} }
Excellent screencast on this by the way.
Add a :limit to the query?