41

私の Rails アプリでは、ユーザーとロールの間に標準的な多対多の関係があります。

class User < ActiveRecord::Base
  has_many :user_roles
  has_many :roles, :through => :user_roles
end

ユーザーに任意のロールを 1 回だけ割り当てることができるようにしたいと考えています。重複を挿入しようとしても、リクエストを無視する必要があります。エラーをスローしたり、検証の失敗を引き起こしたりすることはありません。私が本当に表現したいのは「セット」です。セットに既に存在する要素を挿入しても効果はありません。{1,2,3} U {1} = {1,2,3}、{1,1,2,3} ではありません。

私は次のようにできることに気づきました:

user.roles << role unless user.roles.include?(role)

またはラッパーメソッド(例add_to_roles(role))を作成することによって、しかし私は書くことができるように、関連付けを介して自動化する慣用的な方法を望んでいました:

user.roles << role  # automatically checks roles.include?

そして、それは私のために仕事をします。このようにして、重複をチェックしたり、カスタム メソッドを使用したりすることを覚えておく必要はありません。フレームワークに欠けているものはありますか?最初は has_many の :uniq オプションでできると思っていましたが、基本的には「個別に選択する」だけです。

これを宣言的に行う方法はありますか?そうでない場合は、関連付け拡張機能を使用することでしょうか?

デフォルトの動作が失敗する例を次に示します。

    >> u = User.create
      ユーザー作成 (0.6ms) INSERT INTO "users" ("name") VALUES(NULL)
    => #<ユーザー ID: 3、名前: なし>
    >> u.roles << Role.first
      Role Load (0.5ms) SELECT * FROM "roles" LIMIT 1
      UserRole Create (0.5ms) INSERT INTO "user_roles" ("role_id", "user_id") VALUES(1, 3)
      Role Load (0.4ms) SELECT "roles".* FROM "roles" INNER JOIN "user_roles" ON "roles".id = "user_roles".role_id WHERE (("user_roles".user_id = 3))
    => [#<ロール ID: 1、名前: "1">]
    >> u.roles << Role.first
      Role Load (0.4ms) SELECT * FROM "roles" LIMIT 1
      UserRole Create (0.5ms) INSERT INTO "user_roles" ("role_id", "user_id") VALUES(1, 3)
    => [#<ロール ID: 1、名前: "1">、#<ロール ID: 1、名前: "1">]
4

8 に答える 8

28

追加されたロールが ActiveRecord オブジェクトである限り、何をしているのか:

user.roles << role

:has_manyアソシエーション の重複を自動的に排除する必要があります。

についてhas_many :throughは、次を試してください。

class User
  has_many :roles, :through => :user_roles do
    def <<(new_item)
      super( Array(new_item) - proxy_association.owner.roles )
    end
  end
end

super が機能しない場合は、alias_method_chain をセットアップする必要があるかもしれません。

于 2009-08-22T06:33:39.610 に答える
3

メイン モデルで validates_uniqueness_of とオーバーライド << を組み合わせて使用​​できますが、これにより、結合モデルで他の検証エラーも検出されます。

validates_uniqueness_of :user_id, :scope => [:role_id]

class User
  has_many :roles, :through => :user_roles do
    def <<(*items)
      super(items) rescue ActiveRecord::RecordInvalid
    end
  end
end
于 2011-03-16T19:01:15.897 に答える
2

適切な検証ルールはusers_roles結合モデルにあると思います。

validates_uniqueness_of :user_id, :scope => [:role_id]
于 2009-08-22T06:49:13.650 に答える
0

おそらく、検証ルールを作成することは可能です

validates_uniqueness_of :user_roles

次に、検証例外をキャッチし、正常に続行します。ただし、これは本当にハッキーな感じで、可能であれば非常にエレガントではありません。

于 2009-08-22T05:41:55.847 に答える