Ahoy gem created mutually exclusive migration? - ruby-on-rails

I tried to learn new stuff and use Ahoy gem for my private project. While doing research online, I encountered one repo with Rails 4.2 and Ahoy 1.6 and one thing struck me. Then I started googling and it seems like it's not a single repo issue only.
class CreateVisits < ActiveRecord::Migration
def change
create_table :visits, id: false do |t|
t.uuid :id, default: nil, primary_key: true
(...)
rest of code omitted for readability
Am I missing something, or are those mutually exclusive lines? (not to mention primary key being nil by default?)
I ran almost the same migration locally (without Ahoy gem, with changed table name), and I got nicely-looking db/schema.rb (at first glance - no errors yet), but of course when I try create new object, I hit ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR: null value in column "id" violates not-null constraint error
In my opinion, I'd have write something like this to make it work, or am I missing something really important here that blocks me from persisting an object in DB?
class CreateVisits < ActiveRecord::Migration
def change
create_table :visits do |t|
t.uuid :id, primary_key: true
(...)

Seems like this thing was not related to this Gem, but to other Dev running migration outside Rails migrations and not letting anyone know. This created some confusion between environments.

Related

Using STI fails to migrate database

Been working on this particular issue for a while and thought it was time to ask for help. I've had a look at the following links but no luck
PG undefinedtable error relation users does not exist
https://github.com/rails/rails/issues/3279
And my issue is somewhat similar to this https://github.com/rails/rails/issues/6470 but not exactly.
I have a class called type_tables which is the base class for the STI here is the schema for STI
create_table "type_tables", force: :cascade do |t|
t.string "title"
t.string "desc"
t.string "type"
t.string "uom"
t.index ["title"], name: "index_type_tables_on_title"
t.index ["type"], name: "index_type_tables_on_type"
end
Now, this type table is only a base class in my project and other types such as device_type, certificate_type etc inherit from it.
The model for type_table
class TypeTable < ApplicationRecord
validates :title, presence: true, uniqueness: true
end
The other device_type model
class DeviceType < TypeTable
has_many :certificates
#this is where the error originates
GARMIN_ID = find_by(title: "Garmin").id
#when i use some constant value rails successfully executes the
db:create task in my rake file
GARMIN_ID = 22
end
Now, here's the interesting bit, it only shows this behaviour the first time i,e when there is no existing table in my Postgres database. When I successfully create and migrate my application schema and when the tables are present this line will work everytime.
GARMIN_ID = find_by(title: "Garmin").id
My idea is that since it tries to find the column in the type_tables relation and the relation doesn't exist it throws a no relation exists error. Although the error you see on the console is
2018-08-07 02:24:49.281 UTC [69] FATAL: database "myappdb" does not
exist
mlcot_api | /usr/local/bundle/gems/activerecord-5.1.3/lib/active_record/connection_adapters/postgresql_adapter.rb:699:in
`rescue in connect': FATAL: database "myappdb" does not exist
(ActiveRecord::NoDatabaseError)
When I create the db manually using
docker-compose run api rake db:create
And then run docker-compose up I get
ERROR: relation "type_tables" does not exist at character 566
/usr/local/bundle/gems/activerecord- `async_exec': PG::UndefinedTable:
ERROR: relation "type_tables" does not exist
(ActiveRecord::StatementInvalid)
WHERE a.attrelid = '"type_tables"'::regclass
NOTE: I know the work around and have my app working but I have a dependancy on a particular branch which has all of the commented code
required to build my db for the first time which I want to remove.
Any help appreciated and thanks in advance.
It's a very bad idea to access your database while loading model definitions. Don't do that.
Instead, consider using a class method that only retrieves the value the first time it's needed:
def self.garmin_id
#garmin_id ||= find_by(title: "Garmin").id
end
Personally I'd strongly discourage even that much persistence -- better to spend one query retrieving it fresh for each request that needs it -- but that's more a matter of design judgement.

Trying to db:migrate inside of heroku, migration is failing it

So I have this migration inside of my Rails project:
class CreateSettings < ActiveRecord::Migration[5.2]
def change
create_table :settings do |t|
t.string :frequency
t.text :emails, array: true, default: [].to_yaml
t.integer :reorder
t.timestamps
end
end
end
The default value was originally failing it so I created another migration in order to remove that default value:
class ChangeDefaultColumnForSetting < ActiveRecord::Migration[5.2]
def change
change_column_default(:settings, :emails, nil)
end
end
The schema now looks good and that default value is gone but when I push it up to Heroku and run heroku run rails db:migrate, it fails at the original CreateSettings migration since it still includes the default value. Even if I remove that default value from the first migration manually, I get a "Expected 1 argument but got 0" error inside of Heroku.
Any ideas on how I can go about this? The migration works in development so it must just be a Postgres problem (as I'm using SQLite in dev).
A quick fix is to delete the ChangeDefaultColumnForSetting migration and edit CreateSettings to say:
t.text :emails, array: true, default: []
or
t.text :emails, array: true, default: nil
Then commit the changes and push to Heroku.
After that you really want to stop using SQLite if you're deploying on PostgreSQL. Developing on top of SQLite and deploying on PostgreSQL is going to cause all kinds of problems. You really need to develop, test, and deploy with the same database. Do yourself a big favor and install PostgreSQL locally so that you can test and develop with the same database that you're deploying with.
Alternatively, you could look at Rails.env in your migrations and run different code in different environments. But really, this is a bad idea.

Rails schema dump is putting string in for postgres point.. How do I fix this?

I have a POINT defined in my migrations... when I run the migrations I get a POINT in my database.. Perfect!! Just exactly what I asked for.
But.. What I see in schema.rb is t.string? This messes up my CI system, because it builds the database based on the schema.rb.
Now my co-worker wants to change the type in the database to array, because it would be easy. I find that leaves a stink in my code.
Q: How do I make the schema.rb file show the column as a point?
For completeness
/20141120 ... .rb
class AddHeaderToUser < ActiveRecord::Migration
def change
add_column :users, :header_image_centre, :point
end
end
/schema.rb
t.string "header_image_centre", limit: nil
This seems to be fixed in Rails 4.2

How do I database level contraints to my rails application long after I've migrated everything?

1) I've created an entire Rails JSON API as my first project. It's fairly sizeable, and I put off database level constraints at the time. It's time to do it now.
I have 30+ or so migrations. For example
class CreateItems < ActiveRecord::Migration
def change
create_table :items do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
When I go in and edit
t.string :name
to be
t.string :name, null: false
and run a bundle exec db:reset db:migrate it doesn't work. What do I need to do to add db level constraints to my migrations files, and re migrate them?
2) Also, my 30+ migrations all have a "def change" and that's it. I believe somewhere a long time ago I saw something like def up, def down or something like that. It looks like it was used for rollbacks. Do I need to have "def down" for all of my migrations? Is that how I solve #1? Put "def down" in all of my 30 migrations, rollback the entire thing, then put in my db level constraints, and migrate the whole thing again?
You have two options: you can write new migrations to make changes to the existing columns. In console:
rails g migration add_null_to_name
In the migration file:
class ChangeNumbericFieldInMyTable < ActiveRecord::Migration
def self.up
change_column :my_table, :name, :string, :null => false
end
def self.down
change_column :my_table, :name, :string
end
Rinse and repeat for all the changes you need to make. Note that you can define them in the same migration file, but then if you had to rollback in the future it would roll back all of them.
Your second option is to roll everything back (or just start over), fix your migration methods and remigrate. This might be a cleaner approach, but that's not necessarily a good thing: it would also delete the existing data, if you have any.
I know I said you have two options, but you actually also have a third option, which is to to rely on model validations instead. These are more appropriate in most situations and can make testing much easier. Database validations are more reserved for complex scenarios where the database is used by other applications in addition to yours, and a few other edge cases.
As for your second question, every migration is a two-way street. When you migrate something, you should also be able to roll it back. In a lot of cases Rails can guess the reverse operation. For example, if you add_column in a migration, the opposite of that is obviously remove_column so you don't need to define it explicitly. But certain operations, such as change_column in the example above, don't have an obvious reverse. Rails can understand that something changed, but it can't tell what it changed from. So in those situations you need to explicitly define the down operation. In the case above, it's changing the column back to a state where it doesn't have a validation.

Can I add comments to a table or column using ActiveRecord Migrations?

In MySQL (and other SQL databases), it can be helpful to add comments to a table or column whose purpose may be unclear. (Search MySQL's create table syntax for "comment" for examples.)
Is there a way to do this in an ActiveRecord Migration? I have tried this with no results.
create_table :stuff do |t|
t.integer :obscure_column, :comment => "Explanatory comment"
end
I'm using Rails 3.1.
The migration_comments gem mentioned in a comment to the original question appears to be the best cross-database solution for this need. In addition to providing migrations support for adding table and column comments, the gem also annotates the schema.rb file to include all of the comments. Perfect for my company's needs (large legacy rails app where the database model is ambiguous and also shared with a team of analysts writing native SQL reports).
In Rails 5 you can use the change_column:
class AddCommentsToReferences < ActiveRecord::Migration[5.2]
def up
change_column :references, :achievement_id, :integer, comment: 'Achievement'
change_column :references, :object_id, :integer, comment: 'Achievement object id'
end
end
don't forget write correct column_type as third parameter.
There is a gem called pg_comment that will add this functionality if you are using postgresql.
The gem adds extra commands to add the comments. Note that the syntax in postgresql is different than in mysql, and I guess that is why there is no general ActiveRecord implementation.
For example:
create_table :stuff do |t|
t.integer :some_value
end
set_table_comment :stuff, 'This table stores stuff.'
set_column_comment :stuff, :some_value, 'Stores some value'
This could get pretty verbose, but I know there are some nice tools that make use of this.
Secondly, Rails indeed allows you to manage your schema from within rails (and that is awesome), it may seem sufficient to document your migrations, but after a while nobody looks at the migrations anymore. And you are stuck with an undocumented schema.
In the oracle-enhanced adapter this feature is available from the start, and has just the same syntax as you proposed.
Unfortunately I have not found a similar gem or solution for MySQL.
I don't know when this method is introduced, but in the latest rails (6.0) you can use change_column_comment method.
def change
change_column_comment(:posts, :state, from: "old_comment", to: "new_comment")
end
refer: https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_column_comment

Resources