Rails 4 scope with argument

2020-02-28 03:14发布

问题:

Upgrading Rails 3.2. to Rails 4. I have the following scope:

# Rails 3.2
scope :by_post_status, lambda { |post_status| where("post_status = ?", post_status) }
scope :published, by_post_status("public")
scope :draft, by_post_status("draft")

# Rails 4.1.0
scope :by_post_status, -> (post_status) { where('post_status = ?', post_status) }

But I couldn't find out how to do the 2nd and 3rd lines. How can I create another scope from the first scope?

回答1:

Very simple, just same lambda without arguments:

scope :by_post_status, -> (post_status) { where('post_status = ?', post_status) }
scope :published, -> { by_post_status("public") }
scope :draft, -> { by_post_status("draft") }

or more shorted:

%i[published draft].each do |type|
  scope type, -> { by_post_status(type.to_s) }
end


回答2:

From the Rails edge docs

"Rails 4.0 requires that scopes use a callable object such as a Proc or lambda:"

scope :active, where(active: true)

# becomes 
scope :active, -> { where active: true }


With this in mind, you can easily rewrite you code as such:

scope :by_post_status, lambda { |post_status| where('post_status = ?', post_status) }
scope :published, lambda { by_post_status("public") }
scope :draft, lambda { by_post_status("draft") }

In the event that you have many different statuses that you wish to support and find this to be cumbersome, the following may suit you:

post_statuses = %I[public draft private published ...]
scope :by_post_status, -> (post_status) { where('post_status = ?', post_status) }

post_statuses.each {|s| scope s, -> {by_post_status(s.to_s)} }