Beginner Rails Migration & Column Options in ActiveRecord - sql

Quick question on :null option in ActiveRecord table creation.
Let's say I was to create a new table with a column description.
def change
create_table :products do |t|
t.string :name
t.text :description, null: false
end
end
If I don't have any :presence validation in my model regarding the description column, then shouldn't "nothing" be able to be passed into the description column? I'm not sure what null: false can do to stop me from passing nothing in since I don't have any validation in place.

Rails migration or schema options like null: false operate on database level (storage). If you tried to save record with empty description field, you would receive a database error (e.g. generated by PostgreSQL) wrapped in a ActiveRecord::StatementInvalid:. On calling object.valid? the object would have been valid from the application point of view (Rails).
Rails validations like :presence operate on application level. In this case passing a null value would create an object which would return false on valid? and you could easily access error messages from the object by calling object.errors. If not bypassing validations, Rails would not allow you to save such a record to the database.

Related

Rails removing a column from activerecord not working

I want to remove container:references from my table, I have tried:
rails generate migration RemoveContainerfromCreateTasks container:references
followed by rails db:migrate, but it my reference field is still not removed.
Below is my ActiveRecord
class CreateTasks < ActiveRecord::Migration[6.1]
def change
create_table :tasks do |t|
t.string :title
t.text :body
t.references :container, null: false, foreign_key: true
t.text :tag
t.datetime :due
t.integer :priority
t.timestamps
end
end
end
class RemoveContainerfromCreateTasks < ActiveRecod::Migration[6.1]
def change
end
end
The issue here is really a sneaky capitalization error. Running:
rails generate migration RemoveContainerfromCreateTasks container:references
Will generate a migration with an empty change block which will do absolutely nothing when you migrate it except modify the migrations meta table (a table that AR uses to keep track of which migrations have been run). But if you properly capitalize From:
rails generate migration RemoveContainerFromCreateTasks container:references
It will generate:
class RemoveContainerFromCreateTasks < ActiveRecord::Migration[6.0]
def change
remove_reference :create_tasks, :container, null: false, foreign_key: true
end
end
Rails isn't actually intelligent. It just casts the name argument into snake case and compares it to a set of patterns like:
remove_something_from_tablename foo:string bar:integer
create_tablename foo:string bar:integer
create_foo_bar_join_table foo bar
And it then uses a template to generate the according type of migration. If you don't properly pluralize it will be cast into:
remove_containerfrom_create_tasks
Which Rails does not know what to do with as it does not match a known pattern.
Also note despite popular belief migrations are just a DSL to create SQL transformations which is completely unaware about your tables or models. In this case the resulting migration will just blow up when you attempt to run it since you don't have a create_tasks table.
I would roll the missnamed migration back. Delete it then run:
rails g migration RemoveContainerFromTasks container:references
rails db:migrate
Your issue here is that "CreateTasks" is not table in your database. "Tasks" is, however.
rails g migration RemoveContainerFromTasks container:references
will provide you
class RemoveContainerFromTasks < ActiveRecord::Migration[6.1]
def change
remove_reference :tasks, :container, null: false, foreign_key: true
end
end
A migration of this will successfully remove container from your schema.rb file, and subsequently the database system you're using.
Here's some console output, because why not:
unclecid#home:~/Desktop/sample_app$ rails db:migrate
== 20201227150512 CreateTasks: migrating ======================================
-- create_table(:tasks)
-> 0.0022s
== 20201227150512 CreateTasks: migrated (0.0023s) =============================
== 20201227151021 RemoveContainerFromTasks: migrating =========================
-- remove_reference(:tasks, :container, {:null=>false, :foreign_key=>true})
-> 0.0330s
== 20201227151021 RemoveContainerFromTasks: migrated (0.0331s) ================

What is active record doing on the sql side in a polymorphic association?

I understand what is happening on the ruby level but when a model is represented as a foreign key on its own table, as in "a comment can have many comments" are new sql tables beign created to represent those comments under the hood? I apologize if my question is unclear. Any and all answers are much appreciated.
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :content, {null: false}
t.integer :commentable_id, {null: false}
t.string :commentable_type, {null: false}
t.references :commenter, null: false
t.timestamps(null: false)
end
end
end
Firstly in your migration you can use polymorphic: true to create the polymorphic references:
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :content, {null: false}
t.references :commentable, polymorphic: true, index: true
t.references :commenter, null: false
t.timestamps(null: false)
end
end
end
As you have already determined this will simply generate the :commentable_id and :commentable_type columns in your comments table that represent the polymorphic association - no special tables or other magic going on.
The commentable_type column is simply a string that stores the name of the model that the row belongs_to. The commentable_id is naturally an integer that holds the id of the model instance associated with this polymorphic association.
However, if you really want to find out whats going on under the hood just connect to your database and look at what you have.
Some useful commands to get you started (assuming you have connected to your database using psql) :
\dt (equivalent to SHOW TABLES)
\d+ tablename (equivalent to DESC tablename - where tablename is the name of the table you want information on)
Active Record uses the commentable_type column to constantize it and find the associated record by the commentable_id:
i.e.
commentable_type = "Post" # for example
commentable_type.constantize # returns the Post class model
commentable_type.constantize.find commentable_id # find the Post record
# the above line is equivalent to:
Post.find commentable_id

How do i make rails 3 model type cast according to migration?

The problem that i have is when i run my migrations the updates is applied to the database, but rails does not do the same.
To be more accurate. I have a address model with a house number. Recetly i was told that the house number should be able to contain letters, like (35B). Therefore i would like to convert the integer colum to a column of strings. This is no problem in any case with my data, red. only integers.
The migration that i applied works as expected. It changes the type of the column in the postgres database and preserves the data content. I was using this migration.
class ConvertIntToStringOnNumber < ActiveRecord::Migration
def change
change_table :addresses do |t|
t.change :number, :string
end
end
end
Migration result with this schema.rb
create_table "addresses", :force => true do |t|
t.string "street"
t.string "number"
t.integer "zip"
t.string "floor"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
After running the migration on my heroku server i am not able to query the database using data from the form, this was no problem before. Rails is trying to look for an integer, but the database is containing strings.
Rails is trying to run this query, even though the schema.rb says something different. This is where the party stops.
SELECT "addresses".* FROM "addresses" WHERE "addresses"."street" = 'xxxx' AND "addresses"."number" = 63 AND "addresses"."floor" = '' AND "addresses"."zip" = 9000 LIMIT 1):
I have seen a lot of problems with forigen keys, but this is NOT one of those problems.
Did you restart your application after running the migration?
ActiveRecord loads information about your tables into each class when they are instantiated. See #columns for more info. Since Heroku runs your app in production mode, your classes won't be automatically reloaded on each request.
Try running heroku restart on your application - Rails should pick up the changes then.

Getting paranoia deleted objects through polymorphic relation in Rails 3

I have an Audit class which is used to store action, by and on attributes.
class Audit < ActiveRecord::Base
attr_accessible :activity
belongs_to :by, :polymorphic => true
belongs_to :on, :polymorphic => true
attr_accessible :by, :on
end
The polymorphic association for :by and :on is used to store any kind of objects which should be audited. Mainly because polymorphic is broken down into type and id in the schema and thus should be able to store all my model objects.
create_table "audits", :force => true do |t|
t.string "activity"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "on_id"
t.string "on_type"
t.integer "by_id"
t.string "by_type"
end
The problem I'm having comes from that all of the objects being audited are also using the paranoia gem. The paranoia gem introduces a deleted_at column in each models table which is checked for every query on that model through its default_scope which is set to something like "where(deleted_at is null)". The paranoia gem also provides a method .with_deleted which allows a direct query by turning of the default_scope and as a result also return objects which have beend paranoid / soft deleted.
However if I have any items that are deleted and I try to get all my Audited items listed using.
Audit.all
I can't figure out how to tell Rails to run the lookup query for each polymorphic :by and :on object adding the .with_deleted call. My guess is that rails looks up the the object of a polymorphic relation by
eval(type).find(id)
Which in my case would give me the object with the default_scope of the paranoia gem applied.
I have tried to override self.find_by_sql in Audit but with no luck. I get stuck in some Arel methods which I need to read up on more before I can move on.
I see the following solutions but I can't figure out how to do them.
Overwrite the polymorphic lookup. How?
Get the raw SQL as string before it's evaluated and sub/gsub the Where deleted_at is null part.
Any and all suggestions on how to approach this would be greatly appreciated.
Try this:
def on!
if on_type && on_id
on_type.constantize.with_deleted.find(on_id)
end
end
This will raise an ActiveRecord::RecordNotFound error if the record has been truly deleted, but otherwise will return the "on" object even if it is marked as deleted.

Is it possible to access the parent class (instead of the subclasses) when using single table inheritance?

I have a Post class, with TextPost, ImagePost, and LinkPost subclasses (using STI). These Post types are specified as strings in Post.type (as per STI convention).
I can call TextPost.all, ImagePost.all, and LinkPost.all just fine.
I thought I'd still be able to call Post.all, but I'm getting the following error:
ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'text'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite Post.inheritance_column to use another column for that information.
For reference, here is the relevant part of my schema.rb:
create_table "posts", :force => true do |t|
t.string "title"
t.string "type"
t.integer "author_id"
t.datetime "publish_datetime"
...
end
And my subclasses (each in their own appropriately-named .rb file):
class TextPost < Post
...
end
class ImagePost < Post
...
end
class LinkPost < Post
...
end
Am I doing something wrong? Or is it just not possible to (simply & succinctly) call the parent class when using STI?
Sounds like you have a row in your database with the type column equal to "text". Rails is trying to STI that to a text class. Looks like what you want is TextPost in the type column, not text.