【问题标题】:Don't build another association if one already exists如果一个已经存在,不要建立另一个关联
【发布时间】:2015-05-01 12:21:45
【问题描述】:

我在让嵌套表单工作时遇到了一些问题。在下面的示例中,我有一个可以为帖子定义多个自定义标签的用户。用户应该能够为每个特定标签输入一个值。

所以一个帖子可以有多个标签,但每个标签只能有一个值!例如:帖子可以有一个名为“日期”的标签,也可以有一个名为“心情”的标签。两个标签应该只有一个值。

问题是当用户创建一个标签时——比如说“日期”——应该只能为这个特定标签的帖子输入一个值。因此,如果给定日期值,则表单不应再次为日期构建另一个字段。 (帖子已经有日期)

  1. 用户创建自定义标签(可行)
  2. 在帖子的编辑页面上,用户可以看到他在第 1 步中创建的标签(有效)
  3. 用户可以为每个标签输入一个值(这是问题所在)

我有以下型号:

class User < ActiveRecord::Base
  has_many :posts
  has_many :labels
end

class Post < ActiveRecord::Base
  has_many :label_values
  belongs_to :user
  accepts_nested_attributes_for :label_values, allow_destroy: true
end

class Label < ActiveRecord::Base
  has_many :label_values
  belongs_to :user
end

class LabelValue < ActiveRecord::Base
  belongs_to :post
  belongs_to :label
end

在我的控制器中

class PostsController < ApplicationController

  def edit
    @labels = current_user.labels.all
    @post.label_values.build
  end

end

我的表格:

= simple_form_for @post do |f|
  =f.fields_for :label_values do |s|
    =s.association :label, :include_blank => false
    =s.input :value
 = f.button :submit

像这样,每次用户为特定标签输入一个值时,下一次新的 label_value 再次构建,这不是我想要的。对于每个标签,用户应该能够输入一个值。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 activerecord nested-forms has-many


    【解决方案1】:

    如果您打算使用Label 将元数据附加到Post,您可能需要考虑使用类似于以下的关系:

    class User < ActiveRecord::Base
        has_many :user_labels # Labels which have been used by this user.
        has_many :labels, through: :user_labels
        has_many :posts
    end
    
    class Post < ActiveRecord::Base
        has_many :post_labels
        belongs_to :user, as: :author
        has_many :labels, through: :post_labels
        accepts_nested_attributes_for :post_labels
    
        def available_labels
            Label.where.not(id: labels.pluck(:label_id))
        end
    end
    
    class Label < ActiveRecord::Base
        # @attribute name [String] The name of the label
        has_many :user_labels
        has_many :post_labels
        has_many :users, through: :user_labels
        has_many :posts, through: :post_labels
        validates_uniqueness_of :name
    end
    
    # Used to store label values that are attached to a post
    class PostLabel < ActiveRecord::Base
        belongs_to :post
        belongs_to :label
        validates_uniqueness_of :label, scope: :post # only one label per post
        # @attribute value [String] the value of the label attached to a post
    end
    
    # Used for something like displaying the most used labels by a user  
    class UserLabel < ActiveRecord::Base
        belongs_to :user
        belongs_to :label
    end
    

    在此设置中,标签的作用有点像 Stackoverflow 上的“标签”。每个单独的标签在系统中都是唯一的,但可以附加到许多帖子 - 由许多用户。

    请注意确保这一点的validates_uniqueness_of :label, scope: :post 验证。

    附加到标签的实际值存储在PostLabelvalue 属性中。这种方法的一个主要缺点是您确实受到PostLabelvalue 列类型的限制,并且可能必须进行类型转换。我真的不会将它用于您的示例中的日期,因为很难根据日期进行查询。

    def PostController
    
        def new 
            @available_labels = Label.all()
        end
    
        def edit
            @post = Post.joins(:labels).find(params[:id])
            @available_labels = @post.available_labels
        end
    
        def create
            @post = Post.new(create_params)
    
            if (@post.save) 
              @post.labels.each do |l|
                @post.user.labels << l
              end
              # ...
            else
              # ...
            end
        end
    
        private 
    
        def create_params
            params.permit(:post).allow(:post_labels)
        end
    end
    

    添加

    这只是一些自以为是的建议:

    要为帖子添加新标签,我会在已分配的标签下方添加一个文本输入。 我会使用GET /labels 自动完成。并过滤表单中已经存在的输入。您可以通过向POST /labels 发出 ajax 请求来允许用户动态创建新标签。

    【讨论】:

      【解决方案2】:

      我终于明白了。在我创建的 Post 模型中:

      def empty_labels
          Label.where.not(:id => labelValue.select(:label_id).uniq)
      end
      
      def used_simfields
          LabelValue.where(post_id: id)
      end
      

      然后在视图中我做了:

      = simple_form_for @post do |f|
      
        -@post.used_labels.each do |used_label|
          =f.fields_for :label_values, used_label do |old_label|
            =old_label :value
      
        -@post.empty_labels.each do |empty_label|
          =empty_label.fields_for :label_values, @post.label_values.new do |new_label|
            =new_label.association :label
            =new_label.input :value
      

      我很确定有更好的方法来实现这一点,因此非常欢迎新的想法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-19
        • 1970-01-01
        • 1970-01-01
        • 2012-06-24
        • 1970-01-01
        相关资源
        最近更新 更多