0

アプリをphpからrailsに変換していますが、railsとARの使い方をまだ学んでいます。

簡単に言うと、現在のユーザーがまだメンバーになっていないグループを一覧表示したいと思います。

失敗したアプローチ

  1. Cause.select('causes.*').joins(:users).group('causes.id').where("cause_user_memberships.user_id NOT IN (?)", current_user.id)

  2. Cause.select('causes.*').joins(:users).group('causes.id').where("cause_user_memberships.user_id NOT IN (SELECT cause_user_memberships.cause_id FROM cause_user_memberships WHERE cause_user_memberships.user_id =(?))", current_user.id)

  3. もっとたくさん...

ご協力いただきありがとうございます!

モデルに関するいくつかの情報

User.rb(スニペット)

has_many :cause_user_memberships
has_many :causes, :through => :cause_user_memberships

Cause.rb

attr_accessible :title, :location, :description,...
has_many :cause_user_memberships
has_many :users, :through => :cause_user_memberships

Cause_User_Membership.rb(<-おそらく私の最高のモデル名ではありません)

    # == Schema Information
#
# Table name: cause_user_memberships
#
#  id         :integer          not null, primary key
#  user_id    :integer          not null
#  cause_id   :integer          not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class CauseUserMembership < ActiveRecord::Base
  attr_accessible :cause_id, :user_id

  belongs_to :user 
  belongs_to :cause, :counter_cache => :users_count 
      accepts_nested_attributes_for :cause
      validates_uniqueness_of :user_id, :scope =>[:cause_id]

end

更新:フォローアップ

ダープ、あなたはそれがうまくいったのは正しいです!ありがとう!

フォローアップが少ないので、クエリ時間はかなり長いようです。これは問題を示していますか?各テーブルのレコード数は20未満です。(以下は2つのクエリ結果です。1つは使用する予定のジオコーダーgemを含み、もう1つは含まないものです。少し面倒な場合は申し訳ありません。)

Railsコンソールの場合:

Cause Load (1003.0ms)  SELECT "causes".* FROM "causes" LEFT JOIN cause_user_memberships ON cause_user_memberships.cause_id = causes.id AND cause_user_memberships.user_id = 1 WHERE (cause_user_memberships.id IS NULL)

EXPLAIN (34.3ms)  EXPLAIN SELECT "causes".* FROM "causes" LEFT JOIN cause_user_memberships ON cause_user_memberships.cause_id = causes.id AND cause_user_memberships.user_id = 1 WHERE (cause_user_memberships.id IS NULL)

クエリプラン

 Hash Right Join  (cost=10.45..37.99 rows=1 width=3168)
   Hash Cond: (cause_user_memberships.cause_id = causes.id)
   Filter: (cause_user_memberships.id IS NULL)
   ->  Seq Scan on cause_user_memberships  (cost=0.00..27.50 rows=7 width=8)
         Filter: (user_id = 1)
   ->  Hash  (cost=10.20..10.20 rows=20 width=3168)
         ->  Seq Scan on causes  (cost=0.00..10.20 rows=20 width=3168)
(7 rows)

ローカルホストでGeocoderを使用する場合:

User Load (18.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
  Cause Load (49.6ms)  SELECT causes.*, 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((40.714269 - causes.lat) * PI() / 180 / 2), 2) + COS(40.714269 * PI() / 180) * COS(causes.lat * PI() / 180) * POWER(SIN((-74.005972 - causes.lng) * PI() / 180 / 2), 2))) AS distance, CAST(DEGREES(ATAN2( RADIANS(causes.lng - -74.005972), RADIANS(causes.lat - 40.714269))) + 360 AS decimal) % 360 AS bearing FROM "causes" LEFT JOIN cause_user_memberships 
 ON cause_user_memberships.cause_id = causes.id 
 AND cause_user_memberships.user_id = 1 WHERE (causes.lat BETWEEN 36.37231550667456 AND 45.05622249332544 AND causes.lng BETWEEN -79.73435509229111 AND -68.27758890770889 AND 3958.755864232 * 2 * ASIN(SQRT(POWER(SIN((40.714269 - causes.lat) * PI() / 180 / 2), 2) + COS(40.714269 * PI() / 180) * COS(causes.lat * PI() / 180) * POWER(SIN((-74.005972 - causes.lng) * PI() / 180 / 2), 2))) <= 300) AND (cause_user_memberships.id IS NULL) ORDER BY distance ASC

Completed 200 OK in 1068ms (Views: 49.1ms | ActiveRecord: 791.4ms)

4

1 に答える 1

2

レールでは、joins(:symbol)ステートメントは、存在しない関係を見つけるのに適していない内部結合に変わります。左結合にするために、手動で結合を書くことができます。

Cause.joins("""
  LEFT JOIN cause_user_memberships 
  ON cause_user_memberships.cause_id = causes.id 
  AND cause_user_memberships.user_id = #{current_user.id}
""").where("cause_user_memberships.id IS NULL") 

更新しました

内部結合は、特定のユーザーへのマッピングを持たない一連の原因の作成を防ぎます。例えば:

Causes
id | name
============
1  | Cause 1
2  | Cause 2

Users
id | name
============
1  | User 1

Cause User Memberships
id | user_id | cause_id
=======================
1  | 1       | 1

SELECT *
FROM causes
INNER JOIN cause_user_memberships
ON cause_user_memberships.cause_id = causes.id
AND cause_user_memberships.user_id = 1

戻ります

causes.id | causes.name | cause_user_memberships.id | cause_user_memberships.user_id | cause_user_memberships.cause_id
===========================================================================================================================
1         | Cause 1     | 1                         | 1                              | 1

ユーザーが属していない原因を引き出すために、これ以上ロジックを実行することはできません。

SELECT *
FROM causes
LEFT JOIN cause_user_memberships
ON cause_user_memberships.cause_id = cause.id
AND cause_user_memberships.user_id = 1

戻ります

causes.id | causes.name | cause_user_memberships.id | cause_user_memberships.user_id | cause_user_memberships.cause_id
===========================================================================================================================
1         | Cause 1     | 1                         | 1                              | 1
2         | Cause 2     | null                      | null                           | null

この場合、すべての原因は、cause_user_membershipsテーブル内のレコードと一致するかどうかに関係なく、行を取得します。これで、追加の条件を適用して、ユーザーが属していない原因(cause_user_membership.idがnullの場合)を引き出すことができます。

于 2012-12-04T20:41:43.730 に答える