Rails 3 - Foreign Keys - Strings vs Symbols - ruby-on-rails

I am in the process of learning Rails and I've ran into an interesting problem tonight.
I was creating a migration that would require an index on the foreign key:
Whenever I would run 'bundle exec rake db:migrate', I would receive this console error:
It looks as if it was trying to create the index before it was creating the reference.
The reason I believe this is because when I change the "subject" reference to a symbol:
The migration then suddenly works as expected!
This may just be the fact that I'm a total newby, but are symbols actually processed faster by Ruby than strings are?
Just curious - thanks!

This isn't a "faster" problem, or a problem of speed. The migrations are executed one line at a time, in order. The way you had it specified before simply didn't create the column correctly, hence when it got to the line where you create the index, the names didn't match up.
My guess is, with the string version it created the column name exactly as you spelled it, "subject" as opposed to subject_id when you use a symbol. Either way, you definitely had a name mismatch between when the column was created, and when the index was being built.
Always use symbols for this in your migrations, and you should be fine. Always check your schema.rb file, or browse the database using a GUI tool, after a migration to make sure the columns got created the way you expect, and with the data types you think they are, and you should be good.

Related

How to get the file name of a new migration file using Ruby syntax

So, one of the most frustrating things for me is trying to get the dynamically named migration file after it is created in order to manipulate it in a Ruby on Rails application template.
The problem arises when I run something like run "rails g migration CreateSettings" and then want to replace the created migration file with a pre-made migration file that has default values and null: false etc spelled out. It is quite difficult to know exactly what the file name is as the dynamic timestamp is down to the second.
I did a lot of research through StackOverflow and online and many people kept pointing to doing a Dir.glob to find the file, but the problem with that is that the name is dynamically created and if I have multiple migration files in there, I need to make sure that it is grabbing the correct file to replace.
QUESTION:
What is the best way with Ruby syntax to get the full file name of a dynamically created migration file following a rails g migration call? Say that the file was created after calling run "rails g migration CreateSettings", thus the dynamic name will follow the naming syntax: YYYYMMDDHHmmss_create_settings.rb
VERSIONS
Using Rails -v 6.1.3
Using Ruby -v 2.7.1p83
After a lot of research and trial and error, this is the solution that I came up with.
After running run "rails g migration CreateSettings" I then run this Ruby Code:
1. Dir.chdir('db/migrate/') {
2. Dir.glob('*.rb').each {|filename|
3. if filename.include? "create_settings"
4. file_location = 'db/migrate/' + filename
5. end
6. }
7. }
What this code does is:
(line 1) Changes the directory to look in to the migration folder.
(line 2) Within that directory it finds all files with an rb extension and starts to go through them one at a time (the each statement) with every iteration giving the new filename to the variable filename
(line 3) Checks to see if the filename string includes the string "create_settings"
(line 4) If it does include that string, it saves a new variable for the file path (location)
(lines 5-7) closes the circuits ;-)
Since there should only be one migration file for creating settings, this works. For anyone else using this in the future, make good use of naming conventions. For instance, if you add a new field for the Rails app name to the settings table, you should run the migration "AddAppNameToSettings" NOT "AddFieldToSettings". Because if you do this again and have once again have the second migration name of "AddFieldToSettings", you will have two migration files with different timestamps but with "add_field_to_settings" in the title and the above code will find both files.
I hope that my research and time can be of help to others in the future saving you the time it took me to research this.
Happy coding!

What is the source of "unknown OID" errors in Rails?

When replicating an app to production, my POSTGIS table columns started misbehaving, with Rails informing me there was an "unknown OID 26865" and that the fields would be treated as String.
Instead of current_pos yielding e. g.
#<RGeo::Geographic::SphericalPointImpl:0x22fabdc "POINT (13.39318248760133 52.52908798020595)"> I would get 0101000020E6100000FFDD958664C92A403619DEE6B2434A40. It looked like the activerecord-postgis-adapter was not installed, or installed badly, but I eliminated that possibility by testing for the existence of data type RGeo::Feature::Point and by test-assigning
current_pos = "POINT (13.39318248760133 52.52908798020595)"
to the field - which proceeded without error but then yielded another incomprehensible hex string like the above.
Also, strangely enough, POSTGIS was working correctly within the database, e.g. giving correct results for a ST_DISTANCE query. A very limited problem thus, where writing, writing-parsing (from Point to hex format), manipulating by SQL and reading all worked, only the parsing upon read didn't.
When I tried to use migrations to ensure the database column would have the correct type, the migrations failed, giving
undefined method `st_point' for #<ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition:0x00000005cb80b8>
I spent several hours trying all kinds of solutions, even re-installing the server from scratch, double-checking version numbers of everything, installing a slightly newer version of Ruby and a slightly older version of POSTGIS (to match my other environment), exporting the database and starting with a clean one, and so on. After I had done migrations and arrived at the "undefined method st_point" error, I was finally able to find the solution via Google, way down in a Github issue, and it's really simple:
In config/database.yml, swap out postgres:// for postgis:// in the database url. If you're using Heroku, this may require some ugly manipulation:
production:
url: <%= ENV.fetch('DATABASE_URL', '').sub(/^postgres/, "postgis") %>
So silly...
Do not forget to add activerecord-postgis-adapter to your Gemfile so #Sprachprofi's solution can run.

Why is rails 5 adding nextval method in schema file?

After upgrading to Rails 5 my schema file keeps getting altered when running db:migrate. Rails is changing:
create_table "flightlessons", force: :cascade do |t|
to:
create_table "flightlessons", id: :integer, default: -> { "nextval('lessons_id_seq'::regclass)" }, force: :cascade do |t|
It only occurs on this one model. Why is rails implementing nextval on this particular model? And, why is it getting the model name wrong (lessons_id_seq should be flightlessons_id_seq). Manually changing it to flightlessons_id_seq, however, results in the same no relation error.
PG::UndefinedTable: ERROR: relation "lessons_id_seq" does not exist
To proceed, I simply alter the schema.rb file back to what that line 'should' be. Then, I can migrate or test:prepare or whatever until the next time rails alters it back to using the nextval method.
Thank you for any insight into this.
This is a bit long of an answer, so I've broken it into sections. Buckle up!
My theory
My guess is that your development database does contain the lessons_id_seq sequence, and that its definition of flightlessons.id is set to depend on it (i.e., exactly what Rails is putting into your schema file).
How and why? You likely renamed the lessons table to flightlessons at some point in the past, but that rename didn't change the sequence that the table depended on -- and since schema.rb does not record sequences, the lessons_id_seq sequence does not get copied to your test database, and thus you get this error.
To verify my theory, run rails db and try the following commands:
\d lessons_id_seq
This should return the definition of that sequence. Then, try:
\d flightlessons
And look at the definition of the id column. I expect it to include DEFAULT nextval('lessons_id_seq').
Fixes
The easiest way to fix this is to switch to using structure.sql instead of schema.rb (see the docs). This will carry over the exact state of your database and avoid any interference or interpretation by Rails, which is what's causing your current issue. I always recommend structure.sql for production systems.
However, you can also go into your development database and change the sequence name:
ALTER SEQUENCE lessons_id_seq RENAME TO flightlessons_id_seq;
ALTER TABLE flightlessons ALTER COLUMN id SET DEFAULT nextval('flightlessons_id_seq');
This would be a terrible idea on a production system, but if your issue is just local, it should rectify your current database state with your schema.rb and thus address your current problem. You may wish to encode that into a migration, if you want rails db:drop db:create db:migrate to work on a fresh app.
Why now?
The behavior where Rails is dumping out the default value for your table's primary key may very well be new in Rails 5. Previously, Rails may have just trusted that your ID column had a sane default, and ignored whatever value it actually saw. But I haven't done the research to see if that's true or not.
The simplest fix is to just to rename the sequence in production to match the current table name. E.g. in a production Rails console:
ActiveRecord::Base.connection.execute("ALTER SEQUENCE lessons_id_seq RENAME TO flightlessons_id_seq;")
Turns out it's fine to just rename it, if you haven't done anything fancy with the sequence (like implementing your own Postgres function that references it by name).
Apparently the table points to the sequence by ID, not by name, so the rename is instant and with no ill effects that we could see. More details here: https://dba.stackexchange.com/questions/265569/how-can-i-safely-rename-a-sequence-in-postgresql-ideally-without-downtime
We tried it on staging first, and verified that the ID sequence kept on ticking after making the change in staging and production. Everything just worked.
(Also see Robert Nubel's fantastic answer for more details on what's going on.)

Rails: Is the version number in 'schema.rb' used for anything?

Now that Rails has timestamped migrations, the single version number at the top of /db/schema.rb seems pointless. Sometimes the version number ends up incorrect when dealing with multiple developers or multiple branches.
Does Rails even utilize that :version parameter anymore?
And is there any harm in it being incorrect (as in: it doesn't reflect the timestamp of most recently applied commit)?
Example:
ActiveRecord::Schema.define(:version => 20100417022947) do
# schema definition ...
end
Actually, the version is much more important than this. The code you've cited is actually only a small part of what assume_migrated_upto_version does. The real effect of the migration version is that all prior migrations (as found in the db/migrate directory) are assumed to have been run. (So yes, it does what the function name suggests.)
This has some interesting implications, particularly in the case where multiple people commit new migrations at the same time.
If you version your schema.rb, which is what the Rails team recommends, you're okay. You're 100% guaranteed to have a conflict (the schema version), and the committing/merging user has to resolve it, by merging their changes and setting the :version to the highest of the two. Hopefully they do this merge correctly.
Some projects choose to avoid this continual conflict issue by keeping the schema.rb out of version control. They might rely solely on migrations, or keep a separate version-controlled copy of the schema that they occasionally update.
The problem occurs if someone creates a migration with a timestamp prior to your schema.rb's :version. If you db:migrate, you'll apply their migration, your schema.rb will be updated (but retain the same, higher :version), and everything is fine. But if you should happen to db:schema:load (or db:reset) instead, you'll not only be missing their migration, but assume_migrated_upto_version will mark their migration as having been applied.
The best solution at this point is probably to require that users re-timestamp their migrations to the time of their merge.
Ideally, I would prefer if schema.rb actually contained a list of applied migration numbers rather than an assume-up-to-here :version. But I doubt this will happen -- the Rails team seems to believe the problem is adequately solved by checking in the schema.rb file.
I decided to investigate myself. It turns out that because of the timestamped migrations, the only thing Rails does with that number is assume that the migration with that particular timestamp has already been applied and thus create the appropriate entry in the schema_migration table if it doesn't exist.
from: /lib/active_record/connection_adapters/abstract/schema_statements.rb
def assume_migrated_upto_version(version, migrations_path = ActiveRecord::Migrator.migrations_path)
# other code ...
unless migrated.include?(version)
execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
end
# ...

How to avoid loading my models in a rails Migration

I have a rails project that uses an old versions of the FlexImage plug-in to handle images.
In the old version that image data was stored in a column called "data", in the new version that column must be named "image_file_data".
I wrote a migration to rename the column, but when I try to run the migration, my guess is that rails tries to load the models, which then automatically check to see if the valid column is there (which it isn't) and that throws an error which halts the migration.
I would guess that my problems would be solved if I never loaded the model classes in question and just wrote some sql to rename the columns. But the following line doesn't work, since rails still tries to load the model.
Apartment.connection.execute "ALTER TABLE logos CHANGE DATA image_file_data MEDIUMBLOB;"
Oops, I figured it out. I was calling
model = (table_name.to_s).classify.constantize
Earlier, and this was causing the model to load

Resources