构建复杂的rails SQL

Pet*_*rov 5 sql postgresql activerecord ruby-on-rails ruby-on-rails-3

我正在使用postgreSQL.需要使用punchlines_count获取近期笑话的范围.我的范围已经实现了这一点.我需要知道的是我的穿孔计数器不应该包含有warn_level> 1的穿孔线.警告模型有punchline_id和weight,weight == warn_level.请帮我构建这个查询.澄清:Punchline警告可能是重量为1或2,或者可能有2个警告,每个重量为1.如果warn_level> 1我不应该在我的范围内计算它.谢谢!

我的模特.

class Joke < ActiveRecord::Base
  COLUMNS = self.column_names.map{|c| "jokes.#{c}" }.join(', ') 
  has_many :punchlines, :dependent => :destroy

  scope :recent, :order => 'jokes.created_at DESC'
  scope :recent_jokes_with_punchline_counter, lambda { |limit|
                        select("#{Joke::COLUMNS}, COUNT(punchlines.id) as punchlines_count").
                                             joins(:punchlines).
                                             group(Joke::COLUMNS).limit(limit) }


end

class Punchline < ActiveRecord::Base
  belongs_to :joke
  belongs_to :user
  has_many :warns
end


class Warn < ActiveRecord::Base
  belongs_to :punchline
  belongs_to :user
end
Run Code Online (Sandbox Code Playgroud)

架构:

create_table "jokes", :force => true do |t|
    t.text     "content"
    t.integer  "user_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "up_votes",    :default => 0,     :null => false
    t.integer  "down_votes",  :default => 0,     :null => false
    t.string   "cached_slug"
    t.integer  "popularity"
    t.boolean  "anonymous",   :default => false
    t.string   "shorten_url"
  end

  create_table "punchlines", :force => true do |t|
    t.text     "content"
    t.integer  "user_id"
    t.integer  "joke_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "up_votes",    :default => 0,     :null => false
    t.integer  "down_votes",  :default => 0,     :null => false
    t.string   "cached_slug"
    t.boolean  "anonymous",   :default => false
  end

  create_table "warns", :force => true do |t|
    t.integer "punchline_id"
    t.integer "user_id"
    t.integer "weight"
  end
end
Run Code Online (Sandbox Code Playgroud)

Mat*_*udy 3

我认为你最好通过warn_level在 上创建一个字段来解决这个问题punchlines

就像counterActiveRecord 神奇地为您提供的一样,我们可以做类似的事情。

add_column :punchlines, :warn_level, :integer, :default => 0

class Punchline < ActiveRecord::Base
  def update_warn_level!
    self.update_attribute(:warn_level, self.warns.sum(:weight))
  end
end 
Run Code Online (Sandbox Code Playgroud)

添加警告时,您可以手动调用此方法,或者让观察者为您执行此操作。

http://guides.rubyonrails.org/active_record_validations_callbacks.html#observers

class WarnObserver < ActiveRecord::Observer
  def after_create(model)
    if model.punchline
      model.punchline.update_warn_level!
    end
  end
end

# in your application.rb
config.active_record.observers = :warn_observer
Run Code Online (Sandbox Code Playgroud)

有了这个,你的问题就变得简单多了,我们可以用下面的sql来做你想做的事。

SELECT jobs.*, (
  SELECT COUNT(*) FROM punchlines
  WHERE punchlines.job_id = jobs.id
  AND punchlines.warn_level <= 1
) AS punchline_count
Run Code Online (Sandbox Code Playgroud)

这可以用ActiveRecord来表达

PUNCHLINE_COUNT = <<-SQL
  SELECT COUNT(*) FROM punchlines
  WHERE punchlines.job_id = jobs.id
  AND punchlines.warn_level <= 1
SQL

def with_punchline_count
  select("jobs.*, (#{PUNCHLINE_COUNT}) AS punchline_count")
end
Run Code Online (Sandbox Code Playgroud)

这看起来很混乱,但我认为你给自己提出了一个难题。

希望这对你有用。

注意:您还可以按照类似的方法将 post_count 缓存为列。但让我们迭代地处理这个问题。