Рендеринг частичного с коллекцией через несколько уровней ассоциаций has_many

Я пытаюсь показать коллекцию заказов, которая принадлежит продавцам продуктов сообщения

покупатель - user_id в заказе, продавец - user_id в продукте

Быстро:
У пользователя много продуктов
Продукт имеет много публикаций (для представления продуктов на нескольких мероприятиях)
Сообщение имеет много заказов (другие пользователи могут заказать)

Вот мой код

        class User < ActiveRecord::Base
          # Include default devise modules. Others available are:
          # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
          devise :database_authenticatable, :registerable,
                 :recoverable, :rememberable, :trackable, :validatable

          # Setup accessible (or protected) attributes for your model
          attr_accessible :name, :email, :password, :password_confirmation, :remember_me,
                          :bio, :reason, :barter

                          #:email, :name, :bio, :reason, :barter, :

          has_many :products, :dependent => :destroy
          has_many :posts, :dependent => :destroy
          has_many :orders, :dependent => :destroy

            class Product < ActiveRecord::Base
                belongs_to :user
                belongs_to :club
                belongs_to :category
                has_many :posts, :dependent => :destroy

            class Post < ActiveRecord::Base
                      belongs_to :product, :include => [:user]
                belongs_to :event
                has_many :orders, :dependent => :destroy
                accepts_nested_attributes_for :orders

            class Order < ActiveRecord::Base
                belongs_to :user
                belongs_to :post
#schema
  create_table "orders", :force => true do |t|
    t.float    "quantity"
    t.float    "order_price"
    t.integer  "post_id"
    t.integer  "user_id"
    t.boolean  "payment_received"
    t.integer  "mark_for_buyer"
    t.string   "comment_for_buyer"
    t.integer  "mark_for_seller"
    t.string   "comment_for_seller"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "orders", ["post_id"], :name => "index_orders_on_post_id"
  add_index "orders", ["user_id"], :name => "index_orders_on_user_id"

  create_table "posts", :force => true do |t|
    t.float    "quantity"
    t.datetime "deadline"
    t.integer  "product_id"
    t.integer  "event_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "products", :force => true do |t|
    t.string   "title"
    t.text     "desc"
    t.text     "ingredients"
    t.float    "deposit"
    t.float    "cost"
    t.string   "units"
    t.float    "quantity"
    t.float    "deadline_hours"
    t.boolean  "presell_option"
    t.integer  "user_id"
    t.integer  "category_id"
    t.integer  "club_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

Заказы /index.html.erb

        #this one works beautifully 
        <%= render :partial => "order", :collection => @orders.find_all_by_user_id(current_user.id), :locals => {:order => @order}  %>
        #this one doesn't work (has_many association resources have been tricky for me)
        <%= render :partial => "order", :collection => @orders.where(:post => {:product  => {:user_id => current_user.id}}) , :locals => {:order => @order}  %>

Заказы /_order.html.erb

  <tr>
    <td><%= order.user.name %></td>
    <td><%= order.post.product.user.name %></td>
    <td><%= link_to order.post.product.title, order.post %></td>
    <td><%= number_to_currency order.order_price %></td>
    <td><%= order.quantity %></td>
    <td><%= order.updated_at.to_formatted_s(:short) %> </td>
    <td><%= order.payment_received %></td>
    <td><%= link_to 'Show', order %></td>
    <td><%= link_to 'Edit', edit_order_path(order) %></td>
    <td><%= link_to 'Destroy', order, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>

Теперь отлично работает одна коллекция, где в заказе есть поле user_id, он собирает пользователя, который сделал заказ, и показывает все правильные заказы. Другая коллекция не работает, и я получаю эту ошибку:

ошибка

ActiveRecord::StatementInvalid in Orders#index - No attribute named `user_id` exists for table `product`

Но в таблице "product" есть поле user_id. Я могу вызвать это в cancan, чтобы ограничить управление заказами для пользователя, которому принадлежит продукт, который заказан, и прекрасно работает

can :update, Order, :post => {:product => {:user_id => user.id }}

Любое понимание высоко ценится!

2 ответа

Решение

Я нашел хитрость в том, что этот гем http://rubygems.org/gems/nested_has_many_through может сделать что-то вроде этого:

class Author < User
  has_many :posts
  has_many :categories, :through => :posts, :uniq => true
  has_many :similar_posts, :through => :categories, :source => :posts
  has_many :similar_authors, :through => :similar_posts, :source => :author, :uniq => true
  has_many :posts_of_similar_authors, :through => :similar_authors, :source => :posts, :uniq => true
  has_many :commenters, :through => :posts, :uniq => true
end

class Post < ActiveRecord::Base
  belongs_to :author
  belongs_to :category
  has_many :comments
  has_many :commenters, :through => :comments, :source => :user, :uniq => true
end

Это очень упростило мои запросы и коллекции.

Это весь код, который мне нужен. Мне сказали, что гем nested_has_many_through будет стандартным в Rails 3.1

Модель пользователя

  has_many :products, :dependent => :destroy
  has_many :product_posts, :through => :products, :source => :posts, :uniq => true
  has_many :food_orders, :through => :product_posts, :source => :orders, :uniq => true

OrdersController

 @orders_for_my_products = current_user.food_orders.all

Индекс заказов

<%= render :partial => "order", :collection => @orders_for_my_products, :locals => {:order => @order}  %>

Это возвращает глубоко вложенные данные через отношения с использованием минимального кода. Я очень надеюсь, что это эффективно, но по крайней мере мое здравомыслие сохранено!

Проверьте Закон Деметры. Это объясняет, почему то, что вы делаете, вызывает проблемы (без разделения проблем = спагетти-код). Также проверьте ActiveSupport::Delegate, чтобы увидеть, как вы можете кратко применить Закон Деметры к вашему приложению Rails и избежать списков скучных методов получения и установки.

Несколько заметок, чтобы помочь вам:

  1. Сделайте все ваши звонки ActiveRecord как where в вашем контроллере. Ваше представление должно иметь массивы (или ActiveSupport::Proxy's) для простой итерации и отображения. Вам следует избегать запросов к базе данных, вызывающих представление, и никогда не хранить в них бизнес-логику, извлекающую записи.
  2. Вместо того чтобы бесконечно связывать цепочки ассоциаций моделей, определяйте или делегируйте методы от одной модели к другой. Например, вместо @comment.post.title, определить и использовать @comment.post_title вместо этого (обратите внимание на подчеркивание). Это исключает необходимость наличия у вашего представления врожденных знаний о вашей схеме базы данных и ассоциациях моделей. Нужно просто знать, что комментарий имеет заголовок сообщения, однако данные моделируются.

Если вы проведете рефакторинг своего кода, чтобы каждая модель была сфокусирована на одной проблеме и смоделирована на одном ресурсе, а затем выложите ассоциации поверх этого чистым, разделенным способом, вы наверняка избавитесь от необходимости кого-то решать этот единственный вопрос для вас. (научить человека ловить рыбу...).

Другие вопросы по тегам