Using Postgres Schema with Ruby on Rails - ruby-on-rails

I have a separate server that contains a Ruby on Rails API ( DB is postgres) that will used by multiple, different, applications. I was thinking of using schemas to sort the tables that each application will require and have a "common" schema that contains the tables that the applications will share ( Users, Employees, etc). Each application schema would have roughly 10 tables and the common schema would contain about 15. The current plan is to have about 3-5 apps using this API all using the same common tables.
My question is, is it worth implementing Postgres schemas with Ruby on Rails? I've been looking for some resources on the topic, but there doesn't seem to be much information on it. There are a couple articles written in 2011/2012 but nothing closer to 2018. One of the main things I've been looking for is how to create rails migrations with postgres schemas properly.
I also read a comment from a user stating that they have used postgres and rails separately, but would never use them in conjunction.
It feels like a good idea to use schemas to organize the DB but don't want to go down that road if it will require a lot of manual DB work/maintenance.
Thanks,

This can be easily implemented with Rails, you will have to override the default table name expected by Rails to point to a specific Schema though:
class YourSchemaRecord < ApplicationRecord
self.table_name_prefix = 'name_of_your_schema.'
end
class SomeRecord < ApplicationRecord
end
class YourCommonSchemaRecord < ApplicationRecord
self.table_name_prefix = 'public.'
end
class SomeCommonRecord < YourCommonSchemaRecord
end
About Rails migrations, you can either use plain SQL (my favorite option) or use Rails' built-in method but give the "full path" of the table you want to update the structure:
add_column 'name_of_your_schema.some_records', :some_column, :string

Related

Rails app : What is the best way to add default datas to production application

I have a Rails 6 application used in production.
When I upgrade my app, I add migrations for new tables and/or columns.
What is the best way if I have to add new default datas ?
I use seed file for initial datas, but is there something like migrations (incremental) to add datas ? I know I can add datas in migrations but I would prefer another place for that.
[EDIT]
Do someone know this gem : https://github.com/ilyakatz/data-migrate ?
It seems to do what I am looking for.
In some projects we use to put data updates into db/data-updates/<UTC TimeStamp>_add_thing.rb. We created an own generator for those, called by rails generate data_update AddThing to create a migration calling / executing the contents from the corresponding update file.
Based on that you can put those default datas into db/default-data with similar logic for your new defaults.
You don't need to call them by mirgrations, you also may run them as rails runner db/default-data/123_xyz.rb in your differnt environments.
But if the new defaults are mandatory for your busines logic you should keep them as close as possible to the inventing migration!
If someone is looking for a simple solution, data_migrate does exactly what I wanted.
https://github.com/ilyakatz/data-migrate
Data migrations are stored in db/data.
Data migrations are included in schema migration workflow with simple commands as :
rails db:migrate:with_data
If you can read spanish, this is an interesting documentation on data_migrate : https://la-guia.platan.us/stack/ruby/rails/data_migrate
When it is possible, you should avoid using ActiveRecord models in data migrations. Models are subject to change and if, for example, you have added validations it is possible that your data migrations will not pass.
This is well explained here : https://medium.com/#jeffcoh23/why-you-should-avoid-activerecord-when-using-ruby-on-rails-data-migrate-gem-2651739395d9
A good solution is to call a model created just for data migration :
class AddVisitorRoleToUsers < ActiveRecord::Migration[6.0]
class MigrationUser < ApplicationRecord
self.table_name = :users
end
def up
MigrationUser.all.each do |user|
user.update!(role: 'visitor')
# This will NOT call User model validations
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

Rails: getting info from has_one association located on separate database

I have a problem where I have 2 associated models which are stored in separate databases on the same server.
Say I have two models, City and SportsTeam. I want to be able to find all the sports teams which are nfl teams, and then get their associated cities. I have run into a lot of trouble trying to do this since they are located on separate dbs.
class City
eastablish_connection :city_db
has_one :sports_team
end
class SportsTeam
belongs_to :city
validates :is_nfl_team, :is_mlb_team, :is_nhl_team, presence: true
end
In Rails 5 it's possible, but I would recommend upgrading your app to Rails 6 since you are using multiple databases and Rails 6 support this feature natively.
In Rails 5
You can define another database configuration file, for example, second_database.yml inside the config folder and then load it in an initializer like this: SECOND_DATABASE = YAML::load(ERB.new(File.read(Rails.root.join('config','second_database.yml'))).result)[Rails.env]
Then inside of the models (which its records are in the second database) include this: establish_connection SECOND_DATABASE
Keep in mind that I've worked with multiple databases in Rails 5 and you may run into problems, but what you are trying to do is entirely doable.
In Rails 6
A much better option is to upgrade to Rails 6 and use its multiple database feature, basically what you have to do is:
Define both databases connection inside database.yml.
Add connect_to(:database_one), connect_to(:database_two), etc. at the models depending where their data are.
More details at: https://guides.rubyonrails.org/active_record_multiple_databases.html

Rails Generating Migrations From Model

I've been reading about Rails Migrations to help me start building a rails project.
I'm a bit confused on the generation of the files in db/migrate.
The way I've been designing my application is by starting with the models... outlining all of the objects that I'm going to have in the system as best I can. I would like to generate the migration files FROM these models automatically with the rails migration generator.
Yes, I know "creating migrations manually is easy." And I know I could do it manually, but what I don't understand is why the tool is separated from the pre-created models.
Based on my understanding from the article and other migration questions on SO, I can generate a migration like so:
rails generate migration SomeObj field:string some_other_field:integer
What I don't get is why I need to pass in the fields when my model already exists for SomeObj? Couldn't Rails detect it from the some_obj.rb and create the migration from there?
Also, when I have a more complex model, with has_many, belongs_to, and has_to_and_belongs_to_many relationships, it would be really nice if it autocreated the JOIN tables and fields with the correct names (e.g. foreign_obj_id, foreign_obj_ids)
In a previous project I didn't have to deal with migrations because I used Mongo+Mongoid - and collections were automatically generated due to the nature of how Mongo works (collections that doesn't exist get automatically created upon inserting or updating). Now with this Rails app I'm using Postgres (but I'm sure the same thing would happen with MySQL or any other relational database).
Anyway, is there no helper for this kind of thing? Do I have to create all these migrations manually?
You've got this backwards. You need to build your migrations first, and your models second. Migrations advance the state of the database. Your models reflect the current state of the database. There is not a 1-to-1 mapping of models to migrations.
I would like to generate the migration files FROM these models automatically with the rails migration generator.
Can't be done.
You use rails generate migration to generate the migration, which, almost as a side-effect, generates a stub model file. You cannot use your existing model file to generate the model's migration, because the model doesn't contain any information about the actual columns that make up the model. Rails would have to extract and imply all the necessary information from your associations/validations, and even then it would get most of the columns wrong.
Also, when I have a more complex model,... it would be really nice if it autocreated the JOIN tables and fields with the correct names
Again, you can't. You're responsible for defining your database schema, and the way you do so is by building migrations. You can do so manually, or via rails generate, but the process you should be following is building your migrations first, and your models second.
By way of example, here is a complete model, ready for production:
class MyModel < ActiveRecord::Base
end
That model definition might wrap a table that contains 10 columns or 1000 columns; you (and Rails) have absolutely no way of knowing based on the model definition.
Here's another model, which contains at least one column, but again, you have no way of knowing what kind of column:
class MyModel < ActiveRecord::Base
validates :code, presence: true, uniqueness: true
end
Is code a string or a number? Should there be an index on the column? There is absolutely no way of knowing.

Connect rails to existing postgres DB - models not seen by console and controllers

I have a database with several tables, each one containing columns that may not follow the rails naming convention.
Is there a tool existing to create the ActiveRecord models from those tables or do I need to do this at hand, one by one ?
If I create the ActiveRecord model for one table by hand, would this be ok though ? (no hidden DB identifier needed on top of it ?)
UPDATE
I have tried magicmodels but cannot have it working (it has been a while since it was last modified) and does not seem to be compatible with rails 3.2
What I tried then:
- change the database.yml so it points towards my existing Postresql database
- manually create my models such as:
# app/models/user.rb
class User < ActiveRecord::Base
end
- run the console and tried
User.all
=> I end up with an error saying that contant User was not initialized.
Doesn't the console import the model automatically ? Or is that linked to the fact the configuration I did is not correct ?
http://magicmodels.rubyforge.org/magic_model_generator/ may be what you're looking for. I haven't heard of many tools that give this functionality, though, as many rails apps are designed from scratch instead of given a legacy db and then creating the models from that.
You can easily create models by hand and map them to pretty much any db table. Models have a "set_table_name 'name'" that lets you over-write the rails default convention of a single model mapping to a plural db table name.
ActiveRecord works OK with legacy databases. I did a back-end system that didn't use Rails with ActiveRecord as my ORM. "ActiveRecord Without Rails" got me started. "Using ActiveRecord outside Rails" is also useful. Search Google for "use activerecord without rails" and you'll find even more.
You don't need a fully fleshed out model. Just use a base class for the tables you want and ActiveRecord will query the database for what it needs. It won't know about table relationships, but for general queries it'll do fine. Build the relationships as you go and need them.

Avoiding "Single Table Inheritance" in Rails/ActiveRecord and Legacy Schema

I am trying to integrate with a Legacy table that has a column named "type".
Rails will "smartly" assume that whenever we have a 'type' column in a table, then it will try to use Single Table Inheritance.
Is there anyway to avoid this?
(I can't rename the column).
Well, most of the type it really is smart - convention over configuration has some very real benefits. ;-)
Where convention doesn't work, as in your case, there is (probably - at least, I've always found one so far...) a way around it. For legacy schemas there are a couple of possibilities that spring immediately to mind.
Create a single-table view in your legacy database that remaps columns to avoid AR's conventional names. Not much use if you don't have CREATE VIEW permissions on the DB, of course, or if your DB doesn't have views;
Override the use of :type as the STI indicator using set_inheritance_column, thus
class LegacyValue < ActiveRecord::Base
set_inheritance_column 'does_not_have_one'
end
In Rails 4 set_inheritance_column is deprecated and self.inheritance_column should be used instead:
class LegacyValue < ActiveRecord::Base
self.inheritance_column = 'does_not_have_one'
end

Resources