Reputation: 1894
First, some background.
My application models are like so:
User has_many :subscriptions; has_many :courses, :through => :subscriptions
Subscription belongs_to :course, :user
Course has_many :subscriptions; has_many :users, :through => :subscriptions
Subscription is a join table used to store information such as state (:active, :inactive, :failed, :passed, :unpaid) as well as serve as a connector to all the information that comes with taking a course (such as the Grade model(not yet added), the Payment model(work in progress), etc).
I want to add the Payment model to store a history of payments on a subscription. Each payment can be for one or more subscriptions. So far, I've tried a Payment has_many :subscriptions
, but I'm having trouble with the controller logic and I'm thinking maybe there's a better way to associate the models for "a Payment from a User on a Subscription to a Course."
Technically speaking, in a payment show action, I would need information from each of these models (course.name, user_id of user who paid, payment.amount, all of which are connected to a Subscription) and in a Payment.create action, I'd need to set the state of the subscription (that I can do).
If this is a logical/efficient way to associate the various models, how do I access the needed information(some information from each model course, user, payment, and subscription) in the Payment show action with minimal database calls? I'm using Rails 3.2/Ruby 1.9
and would like to stick with ActiveRecord/ARel instead of straight sql statements. I'm open to "you're doing it wrong" answers as well. If changing the associations will make it more efficient to access the tables from the controller, I'm open to that as well.
Upvotes: 0
Views: 143
Reputation: 1894
@D3mon-1stVFW's answer set me on the right track to figure this problem out.
For the Payment show action, the queries would go something like:
@payment = Payment.find_by_id(params[:id])
@subscriptions = @payment.subscriptions.includes(:course, :user)
This will make it possible to access the course and user data associated with a subscription like so:
<% @subscriptions.each do |subscription|%>
<%= subscription.payment.amount %>
<%= subscription.user.email %>
<%= subscription.course.name %>
<%= subscription.state %>
<% end%>
For a payment new/create action where there is not yet a payment.id
, the queries are slightly different:
@unpaid_subs = Subscription.where(:user_id => current_user.id).unpaid.includes(:course)
@payment = Payment.new
Where current_user
is the user that is going to pay(create a new payment) and .unpaid
is a scope on the Subscription
model that looks like this:
scope :unpaid, -> { where('subscriptions.payment_id' => nil)}
And in the view, you can access the data in the same way as with the show view:
<% @unpaid_subs.each do |sub| %>
<%= sub.course.name %> | <%= sub.course.credits %><br />
<% end %>
My main point of confusion had been that I didn't know you could access parent associations from the child. Subscription belongs_to Courses, yet you can still do subscription.course.something
.
Upvotes: 1
Reputation: 14943
Do (in config/routes.rb
)
resources :users do
resources :courses do
resources :subscriptions
end
end
Which will allow you to do @user.courses.subscriptions
If you want to go from courses
back to users
, for example, you can modify the route to be like
resources :users, :shallow => true do
#...
end
Edit:
For Payment, I would just do like you said has_many :subscriptions
When you show a payment. You would do something like
payment_history = Payment.find(params[:id])
@subscriptions = payment_history.subscriptions
Now you have the list of subscriptions for the link to that payment history, for example
You would loop in the view and display each subscription
<% @subscriptions.each do |subscription|%>
<%= subscription.payment.id %>
<%= subscription.users.size %>
<%= subscription.courses.size %>
<%= subscription.paying_user.name %> <!-- or whereever it exists-->
<% end%>
You need to make sure you have a route for that and restart the server after you change the routes
resources :payments, :shallow => true do
resources :subscriptions
end
Upvotes: 1