Rails 3. Query for not true - ruby-on-rails

I have a table called scheduled_sessions and a Boolean column called instructor_performed to check if the instructor did teach the class or not.
So I need to find all the records where the instructor didn't teach the class. But I can't do this: ScheduledSession.where(:instructor_performed => false) because if the cell is blank, it won't return that record. I just need all records that are NOT true.

It sounds like your instructor_performed column can be true, false, or NULL, so you need to query for false or NULL, like this:
ScheduledSession.where(instructor_performed: [false, nil])
You could avoid this complexity if you'd set up your database table to disallow null values in that column. You can specify this constraint when you create the table in a migration:
add_column :scheduled_session, :instructor_performed, :boolean,
null: false, default: false
or
create_table :scheduled_session do |t|
t.boolean :instructor_performed, null: false, default: false
...
end
Or you can change the constraint for an existing column:
change_column_null :scheduled_session, :instructor_performed, false, false
In all of the above, we're setting the column to allow only true or false values, and we're telling it to use a default value of false. (Without setting the default, you can't add a no-nulls constraint because your existing data violates it.)
I almost always disallow nulls when I'm setting up boolean columns (unless I truly want tri-state attributes), because it lets me do this to find everything that's not true:
ScheduledSession.where(instructor_performed: false)
Note that other answers (now deleted) that encouraged use of an SQL fragment like "instructor_performed != true" won't work because SQL won't let you use = or != to match a NULL value. Kind of weird, but them's the rules. Instead SQL makes you do this:
SELECT * from scheduled_sessions WHERE instructor_performed IS NULL
OR instructor_performed = FALSE;
which the above Rails where query hides from you somewhat, as long as you're still aware that you're searching for two values.

Related

Active Record Where Not boolean: true

I'm struggling to wrap my mind around an ActiveRecord query.
I'm trying to search my database for GolfRetailer objects with ID's 1..100, that have something (not nil) in their :website field, and that don't have true in their duplicate_domain field.
Here's the query I expected to work:
GolfRetailer.where.not(website: nil, duplicate_domain: true).where(id: 1..100)
I also tried this variant of essentially the same query: GolfRetailer.where.not(website: nil).where(id: 1..100, duplicate_domain: !true)
But both return an empty array, despite there definitely being records that meet those requirements.
When I run GolfRetailer.where.not(website: nil).where(id: 1..100) I get an array, and when I run GolfRetailer.where.not(website: nil, duplicate_domain: nil).where(id: 1..100) I also get an array, but with all records that do have the true duplicate_domain flag, which isn't what I'm looking for.
I'd rather not search for records that have duplicate_domain: nil as that's not always correct (I may not have processed their domain yet).
For clarity, here is the Schema for the Model.
create_table "golf_retailers", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "place_id"
t.string "website"
t.string "formatted_address"
t.string "google_places_name"
t.string "email"
t.boolean "duplicate_domain"
t.index ["duplicate_domain"], name: "index_golf_retailers_on_duplicate_domain"
end
What am I missing to make this query work?
This is happening because in SQL when you do a != TRUE, any NULL values will not be included in the result. This is because the NULL value represents an unknown value, so the DB does not know how to do any comparison operations on an unknown value and therefore they're excluded.
One way to get around this is to use IS DISTINCT FROM:
GolfRetailer
.where(id: 1..100)
.where.not(website: nil)
.where("duplicate_domain IS DISTINCT FROM ?", true)
As others have mentioned, you should also ask yourself if it's really the case that it's ever unknown to you if a GolfRetailer has a duplicate_domain.
If, all GolfRetailers with a duplicate_domain of NULL actually mean they don't have one (false) than you should consider preventing a NULL value for that column entirely.
You can do this by adding a NOT NULL constraint on the column with a change_column database migration.
In order to add the NOT NULL constraint you will first need to make sure all of the data in the column has non-null values.
def change
GolfRetailer.in_batches.update_all(duplicate_domain: false)
change_column_null :golf_retailers, :duplicate_domain
end
If your application is under load, you should also be careful about the potential performance any migration like this might have - notably if you add a NOT NULL constraint with a default value.
Consider using something like the Strong Migrations gem to help find DB migrations that might cause downtime before production.

Rails index uniqueness with specific value

I'd like to create a unique index for a table, consisting of 3 columns, but
to check only if one of them has a specific value:
something like
add_index :table, [:col1, :col2, :col3], unique: true
but only if col3 = true,
otherwise I don't care about col1, col2, :col3 = false uniqueness.
is there a way to do it in a migration to keep it at the DB level, or can I only
validate this case in the model?
I don't believe you can have conditional uniqueness constraints at the database layer (via migrations). You can add this as a conditional validation at the AR layer though which should be sufficient for your purposes (though it should be noted this can introduce some race conditions). ie.
validates [:col1, :col2], uniqueness: true, if: ":col3 == true"
Hope that helps.

How to prevent Ruby on Rails Active record from getting default model values from the database?

I'm developing a RoR app, with Firebird with its SQL engine but i cant understand why ActiveRecord (AR) keeps querying the database for default values!
Here is the tables DDL:
CREATE TABLE GLOBAL_SETTINGS
(
SKEY varchar(64) NOT NULL,
SVALUE varchar(256) NOT NULL,
OBS blob sub_type 1,
IS_SYSTEM "BOOLEAN" DEFAULT 1 NOT NULL,
CREATED_AT timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
UPDATED_AT timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT PK_GLOBAL_SETTINGS_SKEY PRIMARY KEY (SKEY)
);
Here is the migration that created this table: (create_global_settings.rb)
class CreateGlobalSettings < ActiveRecord::Migration
def up
create_table :global_settings, :id => false do |t|
t.string :skey, :null => false, :limit => 64
t.string :svalue, :null => false, :limit => 256
t.text :obs
t.boolean :is_system, :null => false, :default => true
t.timestamps :null => false
end
# defaults on timestamp columns
execute("alter table GLOBAL_SETTINGS alter column CREATED_AT set default CURRENT_TIMESTAMP;")
execute("alter table GLOBAL_SETTINGS alter column UPDATED_AT set default CURRENT_TIMESTAMP;")
# our custom PK naming
execute("alter table GLOBAL_SETTINGS add constraint PK_GLOBAL_SETTINGS_SKEY primary key (SKEY)")
end
def down
drop_table :global_settings
end
end
Here is my model: (global_Settings.rb)
class GlobalSettings < ActiveRecord::Base
#model validations!
validates :skey, presence: true
validates :skey, uniqueness: { case_sensitive: false, message: 'Global setting key allready exists!'}
validates :svalue, presence: true
end
No views or tests or helper are defined!
In rails console if i do:
gs = GlobalSettings.new(skey: 'testKey', svalue: 'testValue')
D, [2014-11-21T13:11:18.547669 #7215] DEBUG -- : (192.2ms) SELECT CAST(1 AS SMALLINT) FROM RDB$DATABASE
D, [2014-11-21T13:11:18.564272 #7215] DEBUG -- : (16.3ms) SELECT CAST(CURRENT_TIMESTAMP AS TIMESTAMP) FROM RDB$DATABASE
D, [2014-11-21T13:11:18.580900 #7215] DEBUG -- : (16.4ms) SELECT CAST(CURRENT_TIMESTAMP AS TIMESTAMP) FROM RDB$DATABASE
#<GlobalSettings skey: "testKey", svalue: "testValue", obs: nil, is_system: true, created_at: nil, updated_at: nil>
gs.save
D, [2014-11-21T13:11:24.403986 #7215] DEBUG -- : GlobalSettings Exists (13.2ms) SELECT 1 AS one FROM "GLOBAL_SETTINGS" WHERE LOWER("GLOBAL_SETTINGS"."SKEY") = LOWER(?) ROWS 1, testKey
D, [2014-11-21T13:11:24.543674 #7215] DEBUG -- : SQL (89.4ms) INSERT INTO "GLOBAL_SETTINGS" ("CREATED_AT", "SKEY", "SVALUE", "UPDATED_AT") VALUES (?, ?, ?, ?), 2014- 11-21 13:11:24, testKey, testValue, 2014-11-21 13:11:24
true
As you can see it seems that AR is trying to get the default values for my model/table, and in this case that is not needed because it's querying the database 3 times, and it should only be doing an insert, and letting the SQL engine take care of the rest.
How do i prevent this kind of situation to happen?
Is there any way to prevent AR to fetch defaults for columns?
And another question, in the new method of the GlobalSetting model i am just using columns sKey: and sValue:, why is active record putting all others columns in the insert?
AFAIK: new only creates a model object which accepts a hash as a parameter in which keys are your table attributes. It does not query database at this point of time. Once you call the save method, it first calls valid? method to check if your object passes the validations you have defined. It is independent of database schema except for the uniqueness validation you have defined here. Uniqueness validation queries the database to check if such a value already exists. Once your object passes all the validations, it then calls the query to insert the data into your database. If the data violates some conditions at database level, exception is raised. So when the query is run at the database level, you have no control over it from rails. If there are default values in the database, they'll be assigned. The only way to bypass them is to explicitly passing some other values to the corresponding attribute.
And as per the behaviour of t.timestamps, yes, rails will automatically update the updated_at column each time you modify that record and created_at column when you create a new record.

How to add sequences to a migration and use them in a model?

I want to have a "Customer" Model with a normal primary key and another column to store a custom "Customer Number". In addition, I want the db to handle default Customer Numbers. I think, defining a sequence is the best way to do that. I use PostgreSQL. Have a look at my migration:
class CreateAccountsCustomers < ActiveRecord::Migration
def up
say "Creating sequenze for customer number starting at 1002"
execute 'CREATE SEQUENCE customer_no_seq START 1002;'
create_table :accounts_customers do |t|
t.string :type
t.integer :customer_no, :unique => true
t.integer :salutation, :limit => 1
t.string :cp_name_1
t.string :cp_name_2
t.string :cp_name_3
t.string :cp_name_4
t.string :name_first, :limit => 55
t.string :name_last, :limit => 55
t.timestamps
end
say "Adding NEXTVAL('customer_no_seq') to column cust_id"
execute "ALTER TABLE accounts_customers ALTER COLUMN customer_no SET DEFAULT NEXTVAL('customer_no_seq');"
end
def down
drop_table :accounts_customers
execute 'DROP SEQUENCE IF EXISTS customer_no_seq;'
end
end
If you know a better "rails-like" approach to add sequences, would be awesome to let me know.
Now, if I do something like
cust = Accounts::Customer.new
cust.save
the field customer_no is not pre filled with the next value of the sequence (should be 1002).
Do you know a good way to integrate sequences? Or is there a good plugin?
Cheers to all answers!
I have no suggestions for a more 'rails way' of handling custom sequences, but I can tell you why the customer_no field appears not to be being populated after a save.
When ActiveRecord saves a new record, the SQL statement will only return the ID of the new record, not all of its fields, you can see where this happens in the current rails source here https://github.com/rails/rails/blob/cf013a62686b5156336d57d57cb12e9e17b5d462/activerecord/lib/active_record/persistence.rb#L313
In order to see the value you will need to reload the object...
cust = Accounts::Customer.new
cust.save
cust.reload
If you always want to do this, consider adding an after_create hook in to your model class...
class Accounts::Customer < ActiveRecord::Base
after_create :reload
end
I believe that roboles answer is not correct.
I tried to implement this on my application (exactly the same env: RoR+PostgreSQL), and I found out that when save is issued on RoR with the object having empty attributes, it tries to perform an INSERT on the database mentioning that all VALUES shall be set to NULL. The problem is the way PostgreSQL handles NULLs: in this case, the new row will be created but with all values empty, i.e. the DEFAULT will be ignored. If save only wrote on the INSERT statement attributes filled on RoR, this would work fine.
In other words, and focusing only on the type and customer_no attribute mentioned above, this is the way PostgreSQL behaves:
SITUATION 1:
INSERT INTO accounts_customers (type, customer_no) VALUES (NULL, NULL);
(this is how Rails' save works)
Result: a new row with empty type and empty customer_no
SITUATION 2:
INSERT INTO accounts_customers (type) VALUES (NULL);
Result: a new row with empty type and customer_no filled with the sequence's NEXTVAL
I have a thread going on about this, check it out at:
Ruby on Rails+PostgreSQL: usage of custom sequences
I faced a similar problem, but I also put :null => false on the field hopping that it will be auto-populated with nextval.
Well, in my case AR was still trying to insert NULL if no attribute was supplied in the request, and this resulted in an exception for not-null constraint violation.
Here's my workaround. I just deleted this attribute key from #attributes and #changed_attributes and in this case postgres correctly put the expected sequence nextval.
I've put this in the model:
before_save do
if (#attributes["customer_no"].nil? || #attributes["customer_no"].to_i == 0)
#attributes.delete("customer_no")
#changed_attributes.delete("customer_no")
end
end
Rails 3.2 / Postgres 9.1
If you're using PostgreSQL, check out the gem I wrote, pg_sequencer:
https://github.com/code42/pg_sequencer
It provides a DSL for creating, dropping and altering sequences in ActiveRecord migrations.

How do :default => 0 and :null => false differ for integer fields in migrations?

If I use a migration to update a database, and I add an integer field like this:
t.integer :foo :default => 0, :null => false
What is the default state of existing and new records in the database? I hoping the answer is:
- Both will read back foo as 0.
Is default => 0 necessary, if I have :null => false?
Just trying to understand the difference between the two...
:null => false tells your database not to accept NULL values.
:default => 0 does two things:
Tell your database to use '0' as the default value when NULL or nothing is specified in a query.
Tell rails to use '0' as a default value when creating a new object.
Point 2 makes sure that when you save your new object, you actually have a valid value in place.
To answer your question: If you don't want NULL values in your database, set :null => false, otherwise just use the :default parameter. Mind you, '0' and NULL are not the same things.
Not having NULL values might be important for indexing purposes or if you need to provide direct database access to a third party.

Resources