开发者

How can I filter my query to show instances where current user has no tags?

I'm trying to show only brand instances which the current user has not tagged, even if other users have tagged the same brand already. Something like:

How can I filter my query to show instances where current user has no tags?

Controller

This is my controller code and even though it should be working, it currently returns all brand instances.

@brand = current_user.brands.includes(:taggings).where( [ "taggings.id IS NULL OR taggings.tagger_id != ?", current_user.id ] ).order("RANDOM()").first

Schema (including my join model for good measure)

create_table "brand_users", :force => true do |t|
t.integer  "brand_id"
t.integer  "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end

create_table "taggings", :force => true do |t|
t.integer  "tag_id"
t.integer  "taggable_id"
t.string   "taggable_type"
t.integer  "tagger_id"
t.string   "tagger_type"
t.string   "context"
t.datetime "created_at"
end

add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_co开发者_运维百科ntext"

create_table "tags", :force => true do |t|
t.string "name"
end

end


So if you're using the acts-as-taggable-on gem and have the following models:

class User < ActiveRecord::Base
  acts_as_tagger
  has_many :brand_users
  has_many :brands, :through => :brand_users
end

So you also have the tables in your schema like:

create_table "users", :force => true  do |t|
  t.string "name"
end

create_table "brands", :force => true  do |t|
  t.string "name"
end

Then the following SQL query should hopefully do what you want (?):

SELECT brands.*
FROM brands
WHERE brands.id NOT IN (
    SELECT brands.id
    FROM brands
    INNER JOIN brand_users ON brand_users.brand_id = brands.id
    INNER JOIN taggings ON (taggings.tagger_id = brand_users.user_id AND taggings.tagger_type = 'User')
    WHERE brand_users.user_id = 1 AND taggings.taggable_id = brand_users.brand_id
)

To translate this into Rails ORM, I can't get any closer without hard coding the whole sub-select SQL string, something like:

class Brand < ActiveRecord::Base
  has_many :brand_users
  has_many :users, :through => :brand_users

  scope :has_not_been_tagged_by_user, lambda {|user| where("brands.id NOT IN (SELECT brands.id
    FROM brands
    INNER JOIN brand_users ON brand_users.brand_id = brands.id
    INNER JOIN taggings ON (taggings.tagger_id = brand_users.user_id AND taggings.tagger_type = 'User')
    WHERE brand_users.user_id = ? AND taggings.taggable_id = brand_users.brand_id)", user.id) }

end

(I know you could do this and then use ruby's .map(&:id).join(',') but if this is a large app I think you loose a lot of performance by taking this out of the database, converting it into a string of integers and feeding it back in (as I understand it).)

Then in your controller I think you'd do something like:

@brand = current_user.brands.has_not_been_tagged_by_user(current_user)

As an aside, I think this would actually then execute an SQL like below (is that right?):

SELECT brands.*
FROM users
INNER JOIN brand_users ON brand_users.user_id = users.id
INNER JOIN brands ON brands.id = brand_users.brand_id 
WHERE brands.id NOT IN (
    SELECT brands.id
    FROM brands
    INNER JOIN brand_users ON brand_users.brand_id = brands.id
    INNER JOIN taggings ON (taggings.tagger_id = brand_users.user_id AND taggings.tagger_type = 'User')
    WHERE brand_users.user_id = 1 AND taggings.taggable_id = brand_users.brand_id
) AND users.id = 1


As far as I know there is no SELECT * FROM x WHERE * IS NULL so a procedure would probably be the only way. Just create a procedure in your DB and call it from the code, you don't have to put the SQL in the code.

You can see an example of such a procedure here.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜