Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ActiveRecord - "first" method in "scope" returns more than one record

I'm new to Rails and I have strange problem.

Here is a code example:

class News < ActiveRecord::Base
  scope :pinned, -> { where(pinned: true).first }
end

If there are records with "pinned" flag there is no problem, when I call News.pinned returns a single record.

And I see this query in the log:

SELECT `news`.* 
FROM `news` 
WHERE `news`.`pinned` = 1 
ORDER BY `news`.`id` ASC 
LIMIT 1

But if there are no records with "pinned" flag, when I call News.pinned the next two queries are executed:

SELECT `news`.* 
FROM `news` 
WHERE `news`.`pinned` = 1 
ORDER BY `news`.`id` ASC 
LIMIT 1

SELECT `news`.* FROM `news`

Thanks!

like image 929
Nikolay Slavov Avatar asked Feb 08 '14 18:02

Nikolay Slavov


1 Answers

Friendy, here is the method "scope" of the ActiveRecord:

1   # File activerecord/lib/active_record/scoping/named.rb, line 145
2   def scope(name, body, &block)
3     extension = Module.new(&block) if block
4
5     # Check body.is_a?(Relation) to prevent the relation actually being
6     # loaded by respond_to?
7     if body.is_a?(Relation) || !body.respond_to?(:call)
8       ActiveSupport::Deprecation.warn(
9         "Using #scope without passing a callable object is deprecated. For "                "example `scope :red, where(color: 'red')` should be changed to "                "`    scope :red, -> { where(color: 'red') }`. There are numerous gotchas "                "in the former usage and it makes the implementation more complicated "                "and     buggy. (If you prefer, you can just define a class method named "                "`self.red`.)"
10      )
11    end
12
13    singleton_class.send(:define_method, name) do |*args|
14      if body.respond_to?(:call)
15        scope = all.scoping { body.call(*args) }
16        scope = scope.extending(extension) if extension
17      else
18        scope = body
19      end
20
21      scope || all
22    end
23  end

Note the line 21 if the scope is "nil" then "all" is returned.
In your case, when you call "News.pinned" without records in line 15 the first consult is run and scope receive "nil", so when it gets to line 21, as scope is "nil", "all" is run making the second consult and returning all registers.

I've tested it overwriting the method "scope" by removing the "all" of the line 21 and i had just one query

To bypass this use:

class News < ActiveRecord::Base
  def self.pinned
    where(pinned: true).first
  end
end
like image 98
RMageste Avatar answered Nov 05 '22 12:11

RMageste