Non-standard ActiveRecord
Post from a previous blog
Recently I taught a workshop for a small engineering team in Mexico (all of whom were new to Ruby and Rails) using the Rails Way, and I received a lot of questions about what to do when there is an already existent legacy database with naming standards which add prefixes or suffixes to table names and/or to fields.
They thought that Rails was good for new applications with a new database that follows Rails conventions, which is partially true.
If you follow Rails conventions everything is quite easy, Rails will make a lot of decisions for you. But if you don’t stick to Rails conventions, well then you will have to tell Rails what to do.
Custom table name
When you have an ActiveRecord model, Rails uses the model class name to decide what will be the table name when a query is issued against it. Let’s suppose that we have the following model:
class Photo < ActiveRecord::Base
end
If we query this model with
Photo.all
SELECT `photos`.* FROM `photos`
Rails will issue the query that we see below the #all call, and from that we know that Rails expects a table named photos in our database, Rails pluralized the model name. This is done with the help of ActiveSupport, we can use the #tableize method on a String to let Rails infer a table name from it.
'document'.tableize
=> "documents"
'custom_document'.tableize
=> "custom_documents"
'Custom_Document'.tableize
=> "custom_documents"
From our model we can call #table_name to get the table name:
Photo.table_name
=> "photos"
If our model happened to be in a namespace, then we can add module api.rb and define the following class method to tell Rails what will be the prefix for our model table name:
module Api
def self.table_name_prefix
'api_'
end
end
class Api::Photo < ActiveRecord::Base
end
Api::Photo.table_name
=> "api_photos"
Ok, so all of this is great, but what about having a table name on a legacy database that does not follow Rails conventions?
This is very easy to fix, we just need to set the class attribute #table_name in our model declaration as follows:
class Photo < ActiveRecord::Base
self.table_name = 'tbl_photo'
end
Then if we issue a query against our model, Rails will ignore the convention and use the specified table:
Photo.all
SELECT `tbl_photo`.* FROM `tbl_photo`
Attributes
Rails performs an automatic mapping between the database fields and an ActiveRecord model. The table related to the model is queried for its fields and datatype, and then ActiveRecord adds the pair of getter/setter for those fields dynamically.
Following the example of our photo model where the database fields do not follow Rails conventions:
- str_title
- txt_description
There is not going to be a big issue, the getters/setters will be generated just fine, an instance of our photo model would look like this:
photo = Photo.new
photo.str_title = 'My Cool picture'
photo.txt_description = 'This was taken very far from the city'
The syntax will look very odd for an experienced Rubyist, but we still have a few tricks under our sleeve to make this code look more Ruby-like.
The first option would be to create our getters/setters manually for each field where we want to drop the prefix.
class Photo < ActiveRecord::Base
self.table_name = 'tbl_photo'
def title
read_attribute :str_title
end
def title=(value)
write_attribute :str_title, value
end
end
This will add getters/setters which look more natural, in this case both the :str_title and the :title attributes will be in sync.
photo = Photo.new
photo.title = 'My photo'
photo.str_title
=> 'My photo'
But you can’t use :title for queries, you have to use the real column name for it.
The second option is not as lengthy, and it requires us to use the #alias_attribute method, which will allow us to define an alias for our getters/setters.
class Photo < ActiveRecord::Base
self.table_name = 'tbl_photo'
alias_attribute :title, :str_title
alias_attribute :description, :txt_description
end
Doing this, you will have the same effect as with the first option.
Associations
Another functionality that can be affected by using a database schema standard different from Rails conventions is associations.
Rails infers the association column based on the model name that is being associated, Rails expects to have a column with the model’s name with an _id suffix
class Photo < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :photos
end
In this example, User model will expect Photo model to have a column called photo_id, based on Rails conventions, but we still have the option of specifying a column name for this relationship, in this case our User model will look like:
class User < ActiveRecord::Base
has_many :photos, foreign_key: 'fk_user'
end
Conclusions
If you have a greenfield Rails application, and you stick to Rails conventions, Rails infers a lot of information from your database. But if you work on an application where the database already exists or your company has database conventions that conflict with Rails conventions, you still have a way to have Rails working properly with your database, but you will need to do an extra effort to make it work.
There are a few more options that can help you work with unconventional databases in Rails, where you can have more flexibility with your database schema, without losing Rails features like: SEQUEL and DataMappper.
Both SEQUEL and DataMapper share the ActiveRecord base functionality provided by ActiveModel, so integration with Rails is seamless for both of them.
If you or your team needs training on software development techniques or Ruby on Rails, reach out Michelada.io at michelada.io.