ActiveRecord Query of nil field - ruby-on-rails

This is my test query and result:
>Pack.first.pages
Pack Load (0.3ms) SELECT "packs".* FROM "packs" ORDER BY "packs"."id" ASC LIMIT 1
Page Load (0.1ms) SELECT "pages".* FROM "pages" WHERE "pages"."pack_id" = $1 [["pack_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Page id: 53, created_at: "2015-02-27 21:59:12", updated_at: "2015-03-23 16:41:05", pack_id: 1, name: "test - pack12?", client_name: "", thumbnail: nil, printable_page: nil, preview: nil, vuforia_archive: "dinosaur.zip", unity_bundle: "girl.unity3d", vuforia_identifier: nil, vuforia_archive_updated_at: "2015-02-27 21:59:12", unity_bundle_updated_at: "2015-03-23 16:41:05">]>
The fields I am concerned with are nil, so why doesn't this work...
> Pack.first.pages.where('thumbnail=? OR printable_page=? OR preview=?',nil,nil,nil)
Pack Load (0.3ms) SELECT "packs".* FROM "packs" ORDER BY "packs"."id" ASC LIMIT 1
Page Load (0.1ms) SELECT "pages".* FROM "pages" WHERE "pages"."pack_id" = $1 AND (thumbnail=NULL OR printable_page=NULL OR preview=NULL) [["pack_id", 1]]
=> #<ActiveRecord::AssociationRelation []>

Depending on your version of SQL, you'll need to format the query like so:
Pack.first.pages.where("thumbnail is null or printable_page is null or preview is null")
field = null doesn't work.

Databases implement three-value logic, in which null is neither true nor false. Null cannot be stated to be equal to, not equal to, greater then, or less than any other value, including null, so comparisons with null will always be null, and therefore not true. There is a separate test for nullness: "thumbnail is null".
As a side note, remember that a predicate such as "name = 'jim'" in an RDBMS is a statement of truth which is tested against rows, which are included or not based on whether that statement is true.
As another side note, this means that "age in (1,null)" might be true is age equals 1, but will not be true is age has a value of null. Similarly, "age not in (1, null)" is false for age = 2 because "2 = null" is false.
Active record is generally pretty good at handling this as it can respond to a condition such as where(:age => my_age) by writing different predicates based on whether my_age is null or not. It will even handle my_age being an array of [1, 2, nil] correctly by writing a predicate such as "where age in (1,2) or age is null".
Long story short, use:
Pack.first.pages.where('thumbnail is null OR printable_page is null OR preview is null')

Related

Rails 7 ActiveQuery select with EXISTS returns an object

Trying to run a query for a resource with an additional, computed value. I.e.
Model.where(id: 1)
.select(
'models.*',
'EXISTS(SELECT 1 FROM models WHERE models.id IS NOT NULL) AS computed_value'
)
I would expect to receive the object, with an additional boolean field computed_value.
Instead, the computed_value is being returned as the first record for which the exists statement is true.
i.e.
[
#<Model:<memory_address>
column1: value,
column2: value,
computed_value: #<Model:<memory_address>
]
I'm now looking into other options, but what am I doing wrong here?
SOLVED: This was due to a naming collision. "computed_value" was already an integer column.
I've tested your query on my database (Rails 7 as well) and seems it works as expected. You can find an example bellow
bookings = Booking.where(id: 1)
.select(
'bookings.id',
'EXISTS(SELECT 1 FROM bookings WHERE bookings.id IS NOT NULL) AS computed_value'
)
2022-08-03 08:17:02.466963 D [60306:94480 log_subscriber.rb:130] ActiveRecord::Base -- Booking Load (0.6ms) SELECT "bookings"."id", EXISTS(SELECT 1 FROM bookings WHERE bookings.id IS NOT NULL) AS computed_value FROM "bookings" WHERE "bookings"."id" = $1 [["id", 1]]
=> [#<Booking:0x00000001185a50a0 id: 1>]
[19] pry(main)> booking = bookings.first
=> #<Booking:0x00000001185a50a0 id: 1>
[20] pry(main)> booking.computed_value
=> true
What is strange is that you're getting the computed_value under collection response, which makes me think that you already have such field on the database or so. Aggregated fields shouldn't be visible as the AR collection doesn't know about them but they're accessible on demand.

Escaping Quotes in Rails Query

I have Merchant Table which has the following fields
Merchant(id: integer, name: string, logo: string, description: text, categories: string, created_at: datetime, updated_at: datetime)
I want to run first_or_create query with name attribute. The problem is the name column contains single quotes, for example Brandy's Boy.
mname = "brandy's boy"
conds = "lower(name) = #{mname}"
Merchant.where(conds)
Merchant Load (1.5ms) SELECT "merchants".* FROM "merchants" WHERE "merchants"."deleted_at" IS NULL AND (lower(name) = 'brandy's boy ') LIMIT $1 [["LIMIT", 11]]
OutPut:
#<Merchant::ActiveRecord_Relation:0x000055d0f72a2ce0>
But this gives me the output when I run static query.
Merchant.where("lower(name) = ?", 'Brandy\'s Boy'.downcase)
Merchant Load (3.7ms) SELECT "merchants".* FROM "merchants" WHERE "merchants"."deleted_at" IS NULL AND (lower(name) = 'brandy''s boy') LIMIT $1 [["LIMIT", 11]]
#<ActiveRecord::Relation [#<Merchant id: 1413, name: "Brandy's Boy", logo: nil, description: "ABC", categories: [], created_at: "2020-07-17 07:32:29", updated_at: "2020-07-17 07:32:29">]>
But the variable mname is populated in a loop and it is dynamic. So in that case I need to escape the single quotes and get the desired result (existing Brandy's Boy Merchant Object)
Kindly help.
You don't need to interpolate to create the arguments for where, just bind it/them:
Merchant.where("lower(name) = ?", mname)
Active Record will take care of that and your query will most likely look like this:
SELECT "merchants".* FROM "merchants" WHERE (lower(name) = 'brandy''s boy')
Escaping is doing in the internals of the framework, from the docs:
If an array is passed, then the first element of the array is treated
as a template, and the remaining elements are inserted into the
template to generate the condition. Active Record takes care of
building the query to avoid injection attacks, and will convert from
the ruby type to the database type where needed. Elements are inserted
into the string in the order in which they appear.
User.where(["name = ? and email = ?", "Joe", "joe#example.com"])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe#example.com';
Doing:
Merchant.where(["lower(name) = ?", mname])
Is pretty much the same as doing:
Merchant.where("lower(name) = ?", mname)
So your query is handled as stated above.

Inner joins selecting multiple columns

If I submit
Role.select("roles.character, actors.lname AS actors_lname").joins(:actor)
It returns:
Role Load (0.0ms) SELECT roles.character, actors.lname AS actors_lname
FROM "roles" INNER JOIN "actors" ON "actors"."id" = "roles"."actor_id"
#<ActiveRecord::Relation [#<Role id: nil, character: "Ellis Boyd 'Red' Redding">,
#<Role id: nil, character: "Andy Dufresne">, #<Role id: nil, character: "Warden Norton">]>
Why doesn't the actors.lname column get displayed?
Use select.
Order.select("orders.id, customers.name").joins(:customers)
You can fetch the associated values if you alias them
orders = Order.select("orders.id, customers.name AS customer_name").joins(:customers)
# you must call the method implicitly, or use .attributes
orders.first.customer_name
Please note that the value of customer_name will not show up in the inspection of the record. Therefore the following code
orders.first
in IRB will not print out the attribute.

Using a serializable column and having it resave with incorrect values

I am using something similar to the following structure for a model:
class User < ActiveRecord::Base
serialize :frag, Hash
attr_accessible :name, :frag
end
The frag value is a text field and is a precomputed hash that we can then render via to_json. The problem I am having is that when I save, it resaves the frag value even if it has not changed and it's clear that there is some corruption because it sometimes comes back as a String rather than a Hash.
1.9.3-p547 :004 > u=User.first
User Load (0.2ms) SELECT "users".* FROM "users" LIMIT 1
=> #<User id: 1, name: "Blax", frag: {:name=>"Black Monday"}, created_at: "2014-07-17 01:04:27", updated_at: "2014-07-17 01:10:52">
1.9.3-p547 :005 > u.name="Joe"
(0.1ms) begin transaction
(0.5ms) UPDATE "users" SET "name" = 'Joe', "updated_at" = '2014-07-17 01:11:29.281133', "frag" = '---
:name: Black Monday
' WHERE "users"."id" = 1
(1.3ms) commit transaction
Is there any way to tell rails to bypass saving unchanged values so that it will not try to resave the frag value if it has not changed? It's to the point where I'm thinking of creating a separate class to just manage this cached value.
I really want this:
(0.5ms) UPDATE "users" SET "name" = 'Joe', "updated_at" = '2014-07-17 01:11:29.281133', "frag" = '---
:name: Black Monday
' WHERE "users"."id" = 1
to be this:
(0.5ms) UPDATE "users" SET "name" = 'Joe', "updated_at" = '2014-07-17 01:11:29.281133'
'WHERE "users"."id" = 1
Edit 1
I think the issue is that it applies to_yaml to it each time and wrecks the format eventually.
There are two things going on here in your question.
1. Your serialized value is being updated on every save.
This is actually the intended behavior, and you can read about it in the source. But, let me elaborate:
The way ActiveRecord knows whether an attribute has changed is because a setter method was used.
user = User.first
user.frag = {new: 'hash'} # this calls the setter method User#frag=
user.frag_changed? # => true
However, when serializing a hash, for example, it's very possible for frag to change without calling the setter:
user = User.create name: 'new', frag: {a: 'hash'}
user.frag # => {a: 'hash'}
user.frag[:that] = 'changed'
user.frag # => {a: 'hash', that: 'changed'}
So. Because of this problem, and (I'm guessing) because the performance of doing a full comparison to the current db value was deemed too expensive, what you are seeing is very intentional.
FYI, in Rails 4.1, it does compare the values and this problem goes away.
Also, if I'm understanding your concern, I do not think this is the cause of your actual problem, which I'll get into next.
2. Your serialized value is "sometimes coming back as a String"
This I'm much less sure about. I do know that your Hash is being serialized as a yaml string - that's how serialize works. It would be very helpful if you showed a more concrete example of this "coming back as a string" problem. Maybe you're right and it's yamlifying your value over and over, but that would surprise me under normal use.

Rails - correct db query returns empty array

Can anyone please explain me, why my db queries return empty, when I have data in my table?
Event.all
returns
...
Event Load (0.3ms) SELECT "events".* FROM "events"
=> #<ActiveRecord::Relation [#<Event id: 1, created_at: "2013-08-01 12:27:36", updated_at: "2013-08-01 12:27:36">
...
etc,
While
Event.where(created_at: Date.today)
gives me
Event Load (0.3ms) SELECT "events".* FROM "events" WHERE "events"."created_at" = '2013-08-01'
=> #<ActiveRecord::Relation []>
Where is everything?
The field created_at is a DateTime, but you are comparing it to a Date (no time).
You need to Cast the field as a Date to get all Events created today:
Event.where('CAST(events.created_at as DATE) = ?', Date.today)
Attention: The syntax may change depending on your Data-base system (PostGreSQL / MySQL, etc).
Hope this helps!
Useful link:
http://sqlserverplanet.com/tsql/cast-date
If you look at your actual query -
SELECT "events".* FROM "events" WHERE "events"."created_at" = '2013-08-01'
You are looking for a record with created_at equals 2013-08-01, but in actuality, the record you are trying to search for - the created_at field equals 2013-08-01 12:27:36.
Change your statement to do a search for created_at that contains 2013-08-01
The problem is that the created_at: attribute (assuming that it was created in the migration timestamps) also stores the time. So it will never equal a simple date. You're best option is to parse the date and then compare it.

Resources