GorrillaMcD
GorrillaMcD

Reputation: 1894

How do I access information from multiple associated models in the Controller in an effiecient manner?

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

Answers (2)

GorrillaMcD
GorrillaMcD

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

Sully
Sully

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

Related Questions