Rails Scope returns all instead of nil - ruby-on-rails

I'm running into a strange issue creating a scope and using the first finder. It seems as though using first as part of the query in a scope will make it return all results if no results are found. If any results are found, it will correctly return the first result.
I have setup a very simple test to demonstrate this:
class Activity::MediaGroup < ActiveRecord::Base
scope :test_fail, -> { where('1 = 0').first }
scope :test_pass, -> { where('1 = 1').first }
end
Note for this test, I have set where conditions to match records or not. In reality, I am querying based on real conditions, and getting the same strange behavior.
Here are the results from the failing scope. As you can see, it makes the correct query, which has no results, so it then queries for all matching records and returns that instead:
irb(main):001:0> Activity::MediaGroup.test_fail
Activity::MediaGroup Load (0.0ms) SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1 = 0) ORDER BY "activity_media_groups"."id" ASC LIMIT 1
Activity::MediaGroup Load (0.0ms) SELECT "activity_media_groups".* FROM "activity_media_groups"
=> #<ActiveRecord::Relation [#<Activity::MediaGroup id: 1, created_at: "2014-01-06 01:00:06", updated_at: "2014-01-06 01:00:06", user_id: 1>, #<Activity::MediaGroup id: 2, created_at: "2014-01-06 01:11:06", updated_at: "2014-01-06 01:11:06", user_id: 1>, #<Activity::MediaGroup id: 3, created_at: "2014-01-06 01:26:41", updated_at: "2014-01-06 01:26:41", user_id: 1>, #<Activity::MediaGroup id: 4, created_at: "2014-01-06 01:28:58", updated_at: "2014-01-06 01:28:58", user_id: 1>]>
The other scope operates as expected:
irb(main):002:0> Activity::MediaGroup.test_pass
Activity::MediaGroup Load (1.0ms) SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1 = 1) ORDER BY "activity_media_groups"."id" ASC LIMIT 1
=> #<Activity::MediaGroup id: 1, created_at: "2014-01-06 01:00:06", updated_at: "2014-01-06 01:00:06", user_id: 1>
If I perform this same logic outside of a scope, I get the expected results:
irb(main):003:0> Activity::MediaGroup.where('1=0').first
Activity::MediaGroup Load (0.0ms) SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1=0) ORDER BY "activity_media_groups"."id" ASC LIMIT 1
=> nil
Am I missing something here? This seems like a bug in Rails/ActiveRecord/Scopes to me unless there is some unknown behavior expectations I am unaware of.

This is not a bug or weirdness, after some research i've found its designed on purpose.
First of all,
The scope returns an ActiveRecord::Relation
If there are zero records its programmed to return all records
which is again an ActiveRecord::Relation instead of nil
The idea behind this is to make scopes chainable (i.e) one of the key difference between scope and class methods
Example:
Lets use the following scenario: users will be able to filter posts by statuses, ordering by most recent updated ones. Simple enough, lets write scopes for that:
class Post < ActiveRecord::Base
scope :by_status, -> status { where(status: status) }
scope :recent, -> { order("posts.updated_at DESC") }
end
And we can call them freely like this:
Post.by_status('published').recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published'
# ORDER BY posts.updated_at DESC
Or with a user provided param:
Post.by_status(params[:status]).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published'
# ORDER BY posts.updated_at DESC
So far, so good. Now lets move them to class methods, just for the sake of comparing:
class Post < ActiveRecord::Base
def self.by_status(status)
where(status: status)
end
def self.recent
order("posts.updated_at DESC")
end
end
Besides using a few extra lines, no big improvements. But now what happens if the :status parameter is nil or blank?
Post.by_status(nil).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" IS NULL
# ORDER BY posts.updated_at DESC
Post.by_status('').recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = ''
# ORDER BY posts.updated_at DESC
Oooops, I don’t think we wanted to allow these queries, did we? With scopes, we can easily fix that by adding a presence condition to our scope:
scope :by_status, -> status { where(status: status) if status.present? }
There we go:
Post.by_status(nil).recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC
Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC
Awesome. Now lets try to do the same with our beloved class method:
class Post < ActiveRecord::Base
def self.by_status(status)
where(status: status) if status.present?
end
end
Running this:
Post.by_status('').recent
NoMethodError: undefined method `recent' for nil:NilClass
And :bomb:. The difference is that a scope will always return a relation, whereas our simple class method implementation will not. The class method should look like this instead:
def self.by_status(status)
if status.present?
where(status: status)
else
all
end
end
Notice that I’m returning all for the nil/blank case, which in Rails 4 returns a relation (it previously returned the Array of items from the database). In Rails 3.2.x, you should use scoped there instead. And there we go:
Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC
So the advice here is: never return nil from a class method that should work like a scope, otherwise you’re breaking the chainability condition implied by scopes, that always return a relation.
Long Story Short:
No matter what, scopes are intended to return ActiveRecord::Relation to make it chainable. If you are expecting first, last or find results you should use class methods
Source: http://blog.plataformatec.com.br/2013/02/active-record-scopes-vs-class-methods/

You can use limit instead of first because -
When data not found then first returns nil or first(<number>) returns array which is non chain-able object.
Whereas, limit returns ActiveRecord::Relation object.
More details in this post -
https://sagarjunnarkar.github.io/blogs/2019/09/15/activerecord-scope/

Related

custom type always change the record, leaving it dirty

I have the following custom type:
class EncryptedTextType < ActiveRecord::Type::Text
def deserialize(encrypted_value)
return unless encrypted_value
Encryptor.decrypt(encrypted_value)
end
def serialize(plain_value)
return unless plain_value
Encryptor.encrypt(plain_value)
end
end
ActiveRecord::Type.register(:encrypted_text, EncryptedTextType)
this works fine but always leave my record dirty. every time I load a record from the database that uses this type, it gets dirty instantly.
This is the record:
class Organization < ApplicationRecord
attribute :access_key, :encrypted_text
[1] pry(main)> organization = Organization.last
Organization Load (0.7ms) SELECT "organizations".* FROM "organizations" ORDER BY "organizations"."created_at" DESC, "organizations"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<Organization:0x00007fe000628198
id: "c968db2e-dd5a-4016-bf3d-d6037aff4d7b",
[2] pry(main)> organization.changed?
=> true
[3] pry(main)> organization.changes
=> {"access_key"=>["de07e...", "de07e..."]}
it is weird that even though the access key hasn't changed, AR still thinks it does.
for future reference: I forgot to implement changed_in_place?
def changed_in_place?(raw_old_value, new_value)
raw_old_value != serialize(new_value)
end
this did the trick

Why does storing my ActiveRecord Relation in a memoized variable not work the way I expected?

EDIT: I'm using Ruby 2.3.3 and Rails 5.2.4.3, FYI.
I have an ActiveRecord model called Event more than 7 million records in my database. When I type the following in my Rails console, the Rails logger tells me that an I/O lookup takes place, which takes about 12.0ms:
irb(main):006:0> #events = Event.where("id > 0")
Event Load (12.0ms) SELECT `events`.* FROM `events` WHERE (id > 0) LIMIT 11
My expectation would be that, if I were to conditionally reset #events to another value using an ||= operation (such as #events ||= 'foobar'), I would not see a 2nd Event Load statement logged to the screen (because #events already exists, so the ||= would imply there's no need to evaluate the 2nd half of the expression). However, I do in fact see a 2nd lookup take place:
irb(main):007:0> #events ||= 'foobar'
Event Load (0.5ms) SELECT `events`.* FROM `events` WHERE (id > 0) LIMIT 11
Granted, the lookup is much faster (0.5ms vs 12.0ms), but the fact that the I/O is happening at all is what confuses me. I feel like I'm misunderstanding something basic about how ActiveRecord treats ||= statements, but I'm not sure what that is.
My goal is to cache the results of the 1st ActiveRecord query in the instance variable, such that subsequent references to that instance variable will not invoke additional I/O calls of any kind, and will therefore save the time that would have otherwise been spent on such an I/O call.
EDIT:
Here's a similar version of the complete sequence of commands I entered into my Rails console (this time with my app's Role model instead), along with the abridged results:
irb(main):001:0> #roles = Role.where("id > ?", 0)
(3.9ms) SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci, ##SESSION.sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION', ##SESSION.sql_auto_is_null = 0, ##SESSION.wait_timeout = 2147483
Role Load (22.0ms) SELECT `roles`.* FROM `roles` WHERE (id > 0) LIMIT 11
=> #<ActiveRecord::Relation [#<Role id: 1, name: "Engineering Intern", account_id: 1, created_at: "2013-05-14 23:03:54", updated_at: "2013-05-14 23:03:54", deleted_at: nil>, #<Role id: 2, name: "Operations", account_id: 1, created_at: "2013-05-14 23:04:02", updated_at: "2013-05-14 23:04:02", deleted_at: nil>,
......
irb(main):002:0> #roles ||= :foobar
Role Load (0.4ms) SELECT `roles`.* FROM `roles` WHERE (id > 0) LIMIT 11
=> #<ActiveRecord::Relation [#<Role id: 1, name: "Engineering Intern", account_id: 1, created_at: "2013-05-14 23:03:54", updated_at: "2013-05-14 23:03:54", deleted_at: nil>, #<Role id: 2, name: "Operations", account_id: 1, created_at: "2013-05-14 23:04:02", updated_at: "2013-05-14 23:04:02", deleted_at: nil>,
......
EDIT:
I hypothesized that maybe under-the-hood there could be a subtle (to me, at least) difference between the way the Ruby interpreter reads x ||= y vs. x = x || y, so I also tried #roles = #roles || :foobar, but I still saw a SQL query logged to the REPL.
I think the behaviour you're seeing is related to the console. If you do this in the context of a Rails app, it works as expected. For example, I have a Client model, and I write a `get_them_all' method like this:
def self.get_them_all
#clients = Client.where("id > 52000")
puts "got them"
#clients ||= "foobar"
puts "still have them?"
#clients
end
When I run Client.get_them_all in the Rails console, I see a single query to the database. Interesting, too, is that the single query is run after the two puts statements. Rails only hits the database when it actually has to use the result. Prior to that, it just has what I'll call a nascent query in the #clients variable.
This behaviour means that you can chain the Client#get_them_all method with other query snippets, because it's an ActiveRecord::Relation. So, in the rails console
$> Client.get_them_all.class.name #=> ActiveRecord::Relation, not Array
$> Client.get_them_all.where(lastName: 'Escobar') # I can append 'where'
load(&block) should solve the problem for you.
Causes the records to be loaded from the database if they have not been loaded already. You can use this if for some reason you need to explicitly load some records before actually using them. The return value is the relation itself, not the records.
https://api.rubyonrails.org/v6.1.4/classes/ActiveRecord/Relation.html#method-i-load
#events = Event.where("id > 0").load
#events ||= 'foobar'

Rails Data Modelling: How to establish relationships when 2 Objects "Share" the same has_many list?

In a nutshell, when I create a Transaction Record it has two foreign keys. for the two users that participate in a Transaction, ie:
Now, What I would like to know with your kind help, is How do I establish the relationship(s) between: User and Transaction Models, so that I can easily retrieve ALL the Transactions for either of the two Users.
Something like:
user_one = User.find(1)
user_two = User.find(2)
user_one.transactions # returns all Transactions where user_one.id ==
# user_one_id Or user_one.id == user_two_id
user_two.transactions # returns all Transactions where user_two.id ==
# user_one_id Or user_two.id == user_two_id
What's the best way to achieve this? Is it best to establish foreign keys in the Transaction Model in this case? Or is this a problem to be solved via ActiveRecordQuery only?
Thanks in advance.
If you have two user ids and want to query Transaction on some combination of them, you can use an or clause:
>> Transaction.where(user_one_id: 1).or(Transaction.where(user_two_id: 2))
Transaction Load (4.3ms) SELECT "transactions".* FROM "transactions" WHERE ("transactions"."user_one_id" = $1 OR "transactions"."user_two_id" = $2) LIMIT $3 [["user_one_id", 1], ["user_two_id", 2], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Transaction id: 1, user_one_id: 1, user_two_id: 2, created_at: "2017-09-12 23:25:39", updated_at: "2017-09-12 23:25:39">]>
this is sample code
class Transaction
def move
return ""
end
end
class User1 < Transaction
def move
return 'User1 move: X'
end
end
class User2 < Transaction
def move
return'User2 move: O'
end
end
transactions = [User1.new, User2.new]
transactions.each {|tran|
print tran.move
}
Use polymorfic assossiations that can solve this issue I am in the train now will provide you with code or you can start looking up for this task and good luck ;)

Random ActiveRecord with where and excluding certain records

I would like to write a class function for my model that returns one random record that meets my condition and excludes some records. The idea is that I will make a "random articles section."
I would like my function to look like this
Article.randomArticle([1, 5, 10]) # array of article ids to exclude
Some pseudo code:
ids_to_exclude = [1,2,3]
loop do
returned_article = Article.where(published: true).sample
break unless ids_to_exclude.include?(returned_article.id)
do
Lets look at DB specific option.
class Article
# ...
def self.random(limit: 10)
scope = Article.where(published: true)
# postgres, sqlite
scope.limit(limit).order('RANDOM()')
# mysql
scope.limit(limit).order('RAND()')
end
end
Article.random asks the database to get 10 random records for us.
So lets look at how we would add an option to exclude some records:
class Article
# ...
def self.random(limit: 10, except: nil)
scope = Article.where(published: true)
if except
scope = scope.where.not(id: except)
end
scope.limit(limit).order('RANDOM()')
end
end
Now Article.random(except: [1,2,3]) would get 10 records where the id is not [1,2,3].
This is because .where in rails returns a scope which is chain-able. For example:
> User.where(email: 'test#example.com').where.not(id: 1)
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 AND ("users"."id" != $2) [["email", "test#example.com"], ["id", 1]]
=> #<ActiveRecord::Relation []>
We could even pass a scope here:
# cause everyone hates Bob
Article.random( except: Article.where(author: 'Bob') )
See Rails Quick Tips - Random Records for why a DB specific solution is a good choice here.
You can use some like this:
ids_to_exclude = [1,2,3,4]
Article.where("published = ? AND id NOT IN (?)", true , ids_to_exclude ).order( "RANDOM()" ).first

How to override ActiveRecord method for one Model

I want to override, last method, for my class Calculations.
# app/models/calculations.rb
class Calculations < ActiveRecord::Base
def self.last
console.info "Run my custom Calculations.last"
order(:custom_field).limit(1).last
end
end
1.9.3-p194 :001 > Calculations.last
custom last
Calculations Load (0.1ms) SELECT `calculations`.* FROM `calculations` ORDER BY calculations.created_at DESC LIMIT 1
=> #<Calculations date: "2016-01-13", direction: "order", ...>
1.9.3-p194 :003 > Calculations.where(nil).last
Calculations Load (45.1ms) SELECT `calculations`.* FROM `calculations` ORDER BY `calculations`.`` DESC LIMIT 1
Mysql2::Error: Unknown column 'calculations.' in 'order clause': SELECT `calculations`.* FROM `calculations` ORDER BY `calculations`.`` DESC LIMIT 1
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'calculations.' in 'order clause': SELECT `calculations`.* FROM `calculations` ORDER BY `calculations`.`` DESC LIMIT 1
First example called custom method.
Second called, AR original method.
What did I do wrong?
The problem is that you have overridden .last for the Calculations class, but in your second line you are calling .last on Calculations::ActiveRecord_Relation (returned by the where clause), which is not the one you defined. I am not sure how clean this is, but it should work (no need to override .last in your class too):
class Calculations < ActiveRecord::Base
class ActiveRecord_Relation
def self.last
console.info "Run my custom Calculations.last"
order(:custom_field).limit(1).last
end
end
end
Additional point. If you have no special reasons to use a plural name for your model, consider changing it to singular, which is the rails convention.
To override ::last for Calculations you could do:
class Calculations < ActiveRecord::Base
def self.last
puts "OVERRIDING"
super
end
end
although I wouldn't recommend it.
For what you are doing it looks like a scope might work
scope :last_record, -> { order(created_at: :desc).limit(1) }

Resources