对 Rails 中的复杂关系进行建模

发布于 2024-08-05 07:17:51 字数 3430 浏览 4 评论 0原文

更新

OK, I figured it out. I had to call the following for varieties:

<%=h @user.varieties.find_by_product_id(product.id).name %>

这是我的两个以下问题:

(1)这是否会在编辑/删除记录时导致问题,因为我没有调用联接模型?我看过 Ryan Bates 的一些视频,他在其中强调了这一点,但我在尝试引用此处的连接模型时遇到了麻烦。换句话说,上面的代码应该通过user_products来调用吗?

如果我使用以下引用连接表的代码,我只能让它显示连接表中的 variety_id (因为连接表中没有品种的名称列)。我不确定如何使此代码引用连接表中的 variety_id,然后转到品种表以从“名称”列中获取品种的实际名称。

<% @user.products.each do |product| %>
   <% @user.user_products.find_by_product_id(product.id).variety_id %>
<% end %> 

(2)这个复杂的东西是否正确地放置在视图层中,或者是否有更好的方法将其移动到模型或控制器?

谢谢。

下面的原始问题现已解决...

我有以下型号:
- 用户
- 产品
- 品种 - user_products

这是我正在尝试做的事情的现实世界版本。假设用户是一家杂货店。产品是水果,例如苹果。品种是苹果的类型,例如富士和麦金托什。

我需要创建一个应用程序,其中:

  • 用户可以将多种类型的产品添加到他的页面。产品可以包含多个品种,但用户不需要包含任何品种。例如,Topps Grocery Store 可以将苹果添加到其页面。如果这就是他们想要显示的全部内容,那就没问题。然而,他们还可以通过包括他们携带的苹果类型来添加更多细节,如富士、麦金托什等。品种不能只是一个详细的产品。换句话说,我不能让每种产品都像苹果-富士、苹果-麦金托什那样。它们需要是两个独立的模型。

  • 在用户页面(即“显示”视图)上,我需要能够显示产品和品种(如果有)。系统需要了解品种与该特定用户的特定产品相关联。

根据我收到的第一个答案,我按照下面的答案所述修改了我的模型。每个品种属于一个产品,即fuji只属于apple产品,在产品表中是一个独特的id。而且,一种产品有很多品种,即苹果产品可能有5个或10个不同的品种。

然而,它变得更加复杂,因为每个用户可能有一组不同的产品/品种组合。例如,Topps 杂货店(用户)可能有富士和麦金托什(品种)的苹果(产品)。但是,Publix 杂货店(用户)可能有红色美味且嘎啦(品种)的苹果(产品)。

在用户页面上,我希望能够调用用户携带的每个产品,然后如果用户选择了任何品种,则显示与每个产品相关的品种。

当我在显示视图中尝试下面列出的代码时,出现以下错误: undefined method `user_product' for #:

<% @user.products.each do |product| %>
   <% @user.user_products.find_by_product_id(product.id).varieties %>
<% end %>

另一方面,当我尝试您给我的其他选项(如下所列)时,页面加载并显示sql 查询在日志中似乎是正确的,但页面上没有显示任何品种,这很奇怪,因为我三次检查并且数据库中有应该与查询匹配的记录。详细信息如下...

<% @user.products.each do |product| %>
   <% @user.varieties.find_by_product_id(product.id) %>
<% end %>    

此代码运行以下 sql 查询:

User Load (0.7ms) SELECT * FROM "users" WHERE ("users"."id" = 2)

Variety Load ( 0.5ms) SELECT "varieties".* FROM "varieties" INNER JOIN "user_product" ON "varieties".id = "user_products".variety_id WHERE (("seasons".user_id = 2))

在布局内渲染模板/应用程序
渲染用户/节目

产品加载 (0.7ms) SELECT "products".* FROM "products" INNER JOIN "user_product" ON "products".id = "user_products".product_id WHERE (("user_products".user_id = 2 ))

品种加载 (0.4ms) SELECT "varieties".* FROM "varieties" INNER JOIN "user_product" ON "varieties".id = "user_products".variety_id WHERE ("varieties"."product_id " = 1) AND (("user_products".user_id = 2)) LIMIT 1

品种加载 (0.2ms) SELECT "varieties".* FROM "varieties" INNER JOIN "user_products" ON "varieties" .id = "user_products".variety_id WHERE ("varieties"."product_id" = 2) AND (("user_products".user_id = 2)) LIMIT 1

在上面的例子中,我正在查找的用户user_id=2,他在数据库中有product_id=1 和product_id=2。而且,在 user_products 表中,我确实有一些记录列出了连接到每个 Product_ids 并与某些variety_ids 关联的 user_id。 所以看起来我应该在我的显示视图上显示一些结果,但我我一无所获。

最后,当我尝试以下操作时:

<% @user.products.each do |product| %>
   <%=h @user.user_products.find_by_product_id(product.id) %>
<% end %>

它在每条记录的视图中显示以下内容:#

UPDATE


OK, I figured it out. I had to call the following for varieties:

<%=h @user.varieties.find_by_product_id(product.id).name %>

Here's my two follow questions:

(1) Is this going to cause problems when editing/deleting records because I'm not calling the join model? I've seen some Ryan Bates videos where he stresses this point, but I'm having trouble trying to reference the join model here. In other words, should the above code by called through user_products?

If I use the following code, which references the join table, I'm only able to get it to display the variety_id from the join table (since there's no name column for varieties in the join table). I'm not sure how to make this code reference the variety_id in the join table, but then go to the variety table to get the actual name of the variety from the "name" column.

<% @user.products.each do |product| %>
   <% @user.user_products.find_by_product_id(product.id).variety_id %>
<% end %> 

(2) Is this complex stuff properly placed in the view layer, or is there a better way to move it to the model or controller?

Thanks.

Original question below is now solved ...

I have the following models:
- users
- products
- varieties
- user_products

Here's the real world version of what I'm trying to do. Let's say User is a grocery store. Products are fruits, like apples. And varieties are types of apples, like fuji and mcintosh.

I need to create an app where:

  • A user can add many types of products to his page. The products can include multiple varieties, but the user is NOT required to include any varieties. For example, Topps Grocery Store can add apples to their page. If that's all they want to display, that needs to be ok. However, they can also add more detail by including the types of apples they carry, like fuji, mcintosh, etc. The variety can't just be a detailed product. In other words, I can't make each product be something like apple - fuji, apple - mcintosh. They need to be two separate models.

  • On the user's page (i.e. the "show" view), I need to be able to display both the product and the variety (if any). The system needs to understand that the varieties are connected to the particular product for this particular user.

Following the first answer I received, I revised my models as described in the answer below. Each variety belongs to one product, i.e. fuji belongs to only the apple product, which is a distinct id in the product table. And, a product has many varieties, i.e. the apple product might have 5 or 10 different varieties.

However, it gets more complicated because each user might have a different set of product/variety combinations. For example, Topps grocery store (user) might have apples (product) that are fuji and mcintosh (varieties). But, Publix grocery store (user) might have apples (product) that are red delicious and gala (varieties).

On the user page, I want to be able to call up each product the user carries and then display the varieties connected to each of those products if the user has chosen any varieties.

When I try the code listed below in the show view, I get the following error: undefined method `user_product' for #:

<% @user.products.each do |product| %>
   <% @user.user_products.find_by_product_id(product.id).varieties %>
<% end %>

On the other hand, when I try the other option you gave me (listed below), the page loads and the sql query seems right in the log, but no varieties display on the page, which is weird because I triple checked and there are records in the database that should match the query. Details below...

<% @user.products.each do |product| %>
   <% @user.varieties.find_by_product_id(product.id) %>
<% end %>    

This code runs the following sql query:

User Load (0.7ms) SELECT * FROM "users" WHERE ("users"."id" = 2)

Variety Load (0.5ms) SELECT "varieties".* FROM "varieties" INNER JOIN "user_product" ON "varieties".id = "user_products".variety_id WHERE (("seasons".user_id = 2))

Rendering template within layouts/application
Rendering users/show

Product Load (0.7ms) SELECT "products".* FROM "products" INNER JOIN "user_product" ON "products".id = "user_products".product_id WHERE (("user_products".user_id = 2))

Variety Load (0.4ms) SELECT "varieties".* FROM "varieties" INNER JOIN "user_product" ON "varieties".id = "user_products".variety_id WHERE ("varieties"."product_id" = 1) AND (("user_products".user_id = 2)) LIMIT 1

Variety Load (0.2ms) SELECT "varieties".* FROM "varieties" INNER JOIN "user_products" ON "varieties".id = "user_products".variety_id WHERE ("varieties"."product_id" = 2) AND (("user_products".user_id = 2)) LIMIT 1

In this case above, the user that I'm looking at is user_id=2, he does have product_id=1 and product_id=2 in the database. And, in the user_products table, I do have a few records that list this user_id connected to each of these product_ids and associated with some variety_ids. So it seems like I should be displaying some results on my show view, but I'm getting nothing.

Finally, when I try the following:

<% @user.products.each do |product| %>
   <%=h @user.user_products.find_by_product_id(product.id) %>
<% end %>

It displays the following in my views for each record: #<User_product:0x4211bb0>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

白芷 2024-08-12 07:17:51

我认为你与产品没有多对多的关系。
您拥有该特定品种(如果有)的产品。因此,为了跟踪它,我会使用一个额外的模型。这样您就能够保持产品和品种之间的关系。

模型:

class User < ActiveRecord::Base
  has_many :user_products, :dependent => :destroy
  has_many :products, :through => :user_products
  has_many :varieties, :through => :user_products
end

class UserProduct < ActiveRecord::Base
  belongs_to :user
  belongs_to :product
  belongs_to :variety
end

class Product < ActiveRecord::Base
  has_many :varieties
end

class Variety < ActiveRecord::Base
  belongs_to :product
end

控制器:

class UserProductsController < ApplicationController
    before_filter do 
        @user = User.find(params['user_id'])
    end


    def create
      product = Product.find(params['product_id'])
      variety = Variety.find(params['variety_id']) if params['variety_id']

      @user_product = UserProduct.new
      @user_product.user = user
      @user_product.product = product
      @user_product.variety = variety
      @user_product.save
    end

    def destroy
        # Either do something like this:
        conditions = {:user_id => @user.id}
        conditions[:product_id] = params[:product_id] if params[:product_id]
        conditions[:variety_id] = params[:variety_id] if params[:variety_id]

        UserProduct.destroy_all conditions
    end
end

视图:
如果您不想将品种分组到不同的产品中,而只是将它们作为一个列表,这样就足够了:

# users/show.html.erb
<%= render @user.user_products %>

# user_products/_user_product.html.erb
<%= h user_product.product.name %> 
<%= h user_product.variety.name if user_product.variety %>

否则必须添加一些更复杂的东西。
其中一些可能可以通过使用关联代理来放入模型和控制器,但我无法帮助你

# users/show
<% for product in @user.products do %>
    <%= product.name %>
    <%= render :partial => 'variety', 
        :collection => @user.varieties.find_by_product_id(product.id) %>
<% end %>

# users/_variety
<%= variety.name %> 

将其分成部分当然是不必要的(在这个例子中,可能有点荒谬),但它有助于分离不同的部分,特别是当您想添加更多内容来显示时。

并回答您的问题:

  1. 由于 UserProduct 是一个联接模型,因此您实际上不需要跟踪它的个人 ID。您拥有用户、产品和品种(在这些情况下它存在)。这就是您所需要的,该组合是唯一的,因此您可以找到要删除的记录。此外,永远不应该编辑连接模型(前提是其中的属性不超过这些字段),它要么创建,要么删除,因为它所做的只是保留关联。
  2. 由于它基本上是一个观看对象,因此最好将其放置在视图中。您当然可以使用控制器或模型通过执行以下操作来预先收集所有产品和品种:

将所有逻辑移至控制器和模型可能不是一个好主意,因为它们不应该知道(或关心)您想要如何显示数据。所以,只要准备得足够充分就可以了。

@products_for_user = []
@user.products.each do |product|
 collected_product = {:product => product, 
                      :varieties => @user.varieties.find_by_product_id(product.id)}
  @products_for_user << collected_product
end

They way I see it is that you don't have a many to many relationship to the products.
You have it to the product of that particulary variety (if any). So in order to keep track of it, I would use an extra model. That way you would be able to keep the relationship between products and varieties as well.

The models:

class User < ActiveRecord::Base
  has_many :user_products, :dependent => :destroy
  has_many :products, :through => :user_products
  has_many :varieties, :through => :user_products
end

class UserProduct < ActiveRecord::Base
  belongs_to :user
  belongs_to :product
  belongs_to :variety
end

class Product < ActiveRecord::Base
  has_many :varieties
end

class Variety < ActiveRecord::Base
  belongs_to :product
end

The controller:

class UserProductsController < ApplicationController
    before_filter do 
        @user = User.find(params['user_id'])
    end


    def create
      product = Product.find(params['product_id'])
      variety = Variety.find(params['variety_id']) if params['variety_id']

      @user_product = UserProduct.new
      @user_product.user = user
      @user_product.product = product
      @user_product.variety = variety
      @user_product.save
    end

    def destroy
        # Either do something like this:
        conditions = {:user_id => @user.id}
        conditions[:product_id] = params[:product_id] if params[:product_id]
        conditions[:variety_id] = params[:variety_id] if params[:variety_id]

        UserProduct.destroy_all conditions
    end
end

The views:
If you are not interested in grouping the varieties into the different products and just put them as a list it is enough with this:

# users/show.html.erb
<%= render @user.user_products %>

# user_products/_user_product.html.erb
<%= h user_product.product.name %> 
<%= h user_product.variety.name if user_product.variety %>

Otherwise something a bit more complicated have to be added.
Some of it might be possible to put in the model and controller by using association proxies, but I can't help you there

# users/show
<% for product in @user.products do %>
    <%= product.name %>
    <%= render :partial => 'variety', 
        :collection => @user.varieties.find_by_product_id(product.id) %>
<% end %>

# users/_variety
<%= variety.name %> 

Splitting it up in partials is of course not necessary (and in this example, maybe a bit ridiculous), but it helps in separating the different parts, especially if you want to add more things to display.

And to answer your questions:

  1. Since UserProduct is a join model, you don't really need to keep track of it's individual id. You have the User, the Product and the Variety (in those cases it exists). That's all you need, that combination is unique and thus, you can find the record for deletion. Also, a join model should never be edited (provided that you don't have more attributes in it than these fields), It is either create or delete, since all it do is keep the associations.
  2. Since it is basically a viewing thing it is best to place it in the views. You could of course use the controller or the model to collect all the products and the varieties beforehand by doing something like this:

Moving all of the logic to the controller and the model is probably not a good idea since they are not supposed to know (or care) how you want to display the data. So it is only a matter of preparing it enough.

@products_for_user = []
@user.products.each do |product|
 collected_product = {:product => product, 
                      :varieties => @user.varieties.find_by_product_id(product.id)}
  @products_for_user << collected_product
end
冷弦 2024-08-12 07:17:51

MikeH

您可能在这里有一个拼写错误,@user.user_product 应该是 @user.user_products

当我尝试下面列出的代码时
显示视图,我得到以下内容
错误:未定义方法“user_product”
对于#:

<% @user.products.each do |product| %>  
  <% @user.user_product.find_by_product_id(product.id).varieties %>  
<%结束%>

您还可以考虑分层类型系统,其中只有“Fuji”、“Macintosh”和“Apples”等产品。

然后,“Fuji”和“Macintosh”会将“parent_id”列设置为“Apples”的 id。

只是一个想法。

MikeH

You might just have a typo here, @user.user_product should be @user.user_products

When I try the code listed below in
the show view, I get the following
error: undefined method `user_product'
for #:

<% @user.products.each do |product| %>  
  <% @user.user_product.find_by_product_id(product.id).varieties %>  
<% end %>

Also you might consider a hierarchical type system, where you just have products like "Fuji", "Macintosh", and "Apples".

"Fuji" and "Macintosh" would then have a column "parent_id" set to the id of "Apples".

Just a thought.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文