【问题标题】:Render a partial with a collection through multiple levels of has_many associations通过多个级别的 has_many 关联渲染带有集合的部分
【发布时间】:2026-01-21 20:20:02
【问题描述】:

我正在尝试显示属于卖家产品帖子的订单集合

买家是订单中的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}  %>

orders/_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`

但“产品”表中有一个“用户 ID”字段。我可以在 cancan 中调用它,以将订单管理限制在拥有所订购产品且工作精美的用户身上

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

非常感谢任何见解!

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 collections partial-views has-many-through


    【解决方案1】:

    查看Law of Demeter。它解释了为什么你正在做的事情会导致问题(没有关注点分离=意大利面条代码)。另请查看ActiveSupport::Delegate,了解如何将得墨忒耳法则简明扼要地应用到您的 Rails 应用程序中,并避免列出无聊的 getter 和 setter。

    一些笔记可以帮助你:

    1. 在控制器中进行所有 ActiveRecord 调用,例如 where。您的视图应该有数组(或 ActiveSupport::Proxy 的)来简单地迭代和显示。您应该避免让视图触发数据库查询,并且永远不要在其中保留获取记录的业务逻辑。
    2. 与其在模型关联上无休止地链接,不如从一个模型定义或委托方法到另一个模型。例如,代替@comment.post.title,定义并使用@comment.post_title(注意下划线)。这避免了要求您的视图先天了解您的数据库模式和模型关联。它只需要知道评论有帖子标题,但该数据已建模。

    如果您重构代码以使每个模型都专注于单个关注点并为单个资源建模,然后以干净、分隔的方式将关联分层,那么您肯定不需要有人来解决这个单一的问题向你提问(教人钓鱼……)。

    【讨论】:

    • 这太棒了,我感觉非常接近突破(需要一些好习惯)。关于形成查询的任何提示?我重构让@order.post_product_user_id 出现在我的索引中(它有效),但是当我查询Order.where(:post_product_user_id => current_user.id)时,我得到“表orders 不存在名为post_product_user_id 的属性”。我的研究中还没有真正出现,但感谢您为我指明了正确的方向!
    • 听起来你仍然在链接模型。在您的控制器操作中,您应该使用@orders = current_user.orders 之类的方式处理获取记录。我不明白为什么当您的 User 和 Order 模型直接关联时,您甚至要触摸 Post 或 Product 模型来访问您的用户。
    • 我这样做是为了保持编程干燥。与订单关联的用户是买方,并且易于访问。卖家与产品相关联,我不想在订单中重复卖家 ID,因为它已经可以通过关联访问。这是对 DRY 的适当使用吗?
    • 我不认为 DRY 是您在这里应用的概念,但我认为您应该给它们适当的名称,例如belongs_to :buyer, :class =&gt; 'User'(这意味着你的外键是buyer_id)。本质上,您需要尽可能地将模型彼此分开;他们只需要知道与他们直接相关的模型有哪些可用。所以order.products.each { |prod| prod.seller_name } 与你想得到的一样相互关联。
    • 你认为github.com/ianwhite/nested_has_many_through gem 会做我正在尝试的事情吗?有人告诉我这是 rails 3.1 的标准配置
    【解决方案2】:

    我发现诀窍是这个 gem 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
    

    这极大地简化了我的查询和集合。

    这就是我最终需要的所有代码。我听说 gemnested_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
    

    订单控制器

     @orders_for_my_products = current_user.food_orders.all
    

    订单索引

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

    这会使用最少的代码通过关系返回深层嵌套数据。我当然希望它有效,但至少我的理智得以幸免!

    【讨论】: