Rails 4中连接表的默认范围

时间:2013-12-03 22:29:22

标签: ruby-on-rails activerecord ruby-on-rails-4

我必须只选择属于ctype = TV

的频道的节目时间表

但是我收到了这个错误:

enter image description here

计划(型号)

class Program < ActiveRecord::Base
  has_many :program_schedules, dependent: :delete_all

ProgramSchedule (型号)

class ProgramSchedule < ActiveRecord::Base
  belongs_to :program
  belongs_to :channel


  default_scope { includes(:channel).where("program_schedules.channels.ctype NOT IN (?)", ['tv']) }

end

频道(型号)

class Channel < ActiveRecord::Base

  validates :name, :site, :ctype, :country, :presence => true

  has_many :programs, :dependent => :delete_all

QUERY

@programs = Program.includes(:program_schedules).where(
                             "programs.ptype" => ["tv_show","movie"],
                                "programs.country" => @country).
                          select("programs.id").
                          group("programs.id, program_schedules.id, channels.id").
                          having("program_schedules.stop > ?", Time.current)

Schema.rb

  create_table "channels", force: true do |t|
    t.string   "site"
    t.string   "name"
    t.string   "icon"
    t.string   "ctype"
    t.string   "country"
    t.string   "lang"
    t.integer  "ordertab"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.boolean  "homepage"
  end

      create_table "programs", force: true do |t|
    t.string "country"
    t.string "title"
    t.text   "subtitle"
    t.text   "description"
    t.float  "official_rating", default: 0.0
    t.float  "rating",          default: 0.0
    t.string "ptype"
    t.string "year"
    t.string "imdb"
    t.string "tmdb"
    t.string "tvdb"
    t.string "wiki"
    t.string "poster"
    t.string "backdrop"
    t.string "trailer"
    t.float  "popularity",      default: 0.0
  end

  create_table "program_schedules", force: true do |t|
    t.integer  "program_id"
    t.datetime "start"
    t.datetime "stop"
    t.integer  "channel_id"
  end

4 个答案:

答案 0 :(得分:2)

您的includes应该包含关联,而不是符号形式的表名。尝试将默认范围更改为以下内容:

default_scope { includes(:channel).where("channels.ctype NOT IN (?)", ['tv']) }

答案 1 :(得分:2)

我将此作为“回答”发布,但它的目的是作为评论。我这样做是为了让我能更好地说明我的解释。如果您希望我删除此无答案,请告诉我。

我试图复制您的问题,而我根本无法使用您迄今为止发布的default_scope查询。最终,问题是您的LEFT OUTER JOIN条件引用了尚未加入的表。即使它在查询的后期加入,解释器也看不到那么遥远。

这是我所指的部分(为便于阅读而格式化):

...FROM "programs" 
    LEFT OUTER JOIN 
        "program_schedules" 
            ON "program_schedules"."program_id" = "programs"."id" 
                AND (channels.ctype NOT IN ...)
                     ^^^
                     Problem is here. The channels table has not yet been joined.

此外,我从未看到.where附加到连接条件。如果您正在使用该方法,ActiveRecord 将您传递给.where子句中的WHERE方法的任何内容放置,如您所期望的那样。出于某种原因,显然是将LEFT OUTER JOIN作为AND条件附加条件。

我知道向连接添加条件的唯一方法是使用.joins方法,如下所示:

...joins(" AND program_schedules.channels.ctype NOT IN (?)", ['tv'])

这确实会导致像这样的查询(为简洁而截断):

LEFT OUTER JOIN "program_schedules" ON ... AND program_schedules.channels......

此外,错误消息中生成的查询说明

(channels.ctype NOT IN ...)

作为失败的连接条件,而您的.where条件状态

program_schedules.channels.ctype

我没有在你的问题的其他地方看到你在错误信息中写了任何看起来像查询部分的内容。所以,似乎我们并没有完全看到一切。

似乎我们仍然遗漏了一些信息。您是否有可能确实有类似我上面写的.join条件?其他模型中是否有其他default_scopes可以执行此操作?

答案 2 :(得分:1)

当我研究我在我的应用程序中看到的类似问题时,我偶然发现了这个StackOverflow问题。我认为default_scopejoins一起使用只是打破了。

我将你的代码简化为一些简单的失败,运行Program.joins(:program_schedules).to_sql并得到以下sql:

SELECT "programs".*
FROM "programs"
INNER JOIN "program_schedules" ON
  "program_schedules"."program_id" = "programs"."id" AND 
  ("channels"."ctype" NOT IN ('tv'))

问题还在于它还没有加入channels,而是从不加入频道。 default_scope中的where子句被添加到INNER JOIN,但联接被丢弃了。

class Channel < ActiveRecord::Base
  has_many :programs
end

class Program < ActiveRecord::Base
  has_many :program_schedules
end

class ProgramSchedule < ActiveRecord::Base
  belongs_to :program
  belongs_to :channel

  default_scope { joins(:channel).where.not(channels: { ctype: ['tv'] }) }
end

ActiveRecord::Schema.define(version: 20140331124327) do
  create_table "channels", force: true do |t|
    t.string   "site"
    t.string   "name"
    t.string   "icon"
    t.string   "ctype"
    t.string   "country"
    t.string   "lang"
    t.integer  "ordertab"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.boolean  "homepage"
  end

  create_table "programs", force: true do |t|
    t.string "country"
    t.string "title"
    t.text   "subtitle"
    t.text   "description"
    t.float  "official_rating", default: 0.0
    t.float  "rating",          default: 0.0
    t.string "ptype"
    t.string "year"
    t.string "imdb"
    t.string "tmdb"
    t.string "tvdb"
    t.string "wiki"
    t.string "poster"
    t.string "backdrop"
    t.string "trailer"
    t.float  "popularity",      default: 0.0
  end

  create_table "program_schedules", force: true do |t|
    t.integer  "program_id"
    t.datetime "start"
    t.datetime "stop"
    t.integer  "channel_id"
  end
end

答案 3 :(得分:1)

请查看:https://github.com/rails/rails/issues/12896

在activerecord-4.1.7 / lib / active_record / scoping / default.rb中,有下一条评论:

# If you need to do more complex things with a default scope, you can
# alternatively define it as a class method:
#
class Article < ActiveRecord::Base
  def self.default_scope
    # Should return a scope, you can call 'super' here etc.
  end
end

这对我有用。