Rails application with legacy database - ruby-on-rails

I'm building an API that will look for data in a "non-rails-database". It already exists and was not created with the rails project. What is the best approach to use in this case ? Let's say I have a table called User in this database, I would like to be able to do something like this in the controller: User.last or User.find(id). It would be good, because I have to send back to the frontend as a JSON.
Should I use PORO Models ? How would it be ?

Create your models then set table name and primary key explicitly, this helps you call active record methods in a controller
class User < ApplicationRecord
self.primary_key = 'user table primary key'
self.table_name = 'user table name'
end
ref http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/PrimaryKey/ClassMethods.html
http://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-table_name-3D

I would personally prefer to have them in PORO. For me, it makes things clear and gives more flexibility.
For an example, I would have a seperate folder called api_models in the app folder and have the classes there.
First, it may feel like you are duplicating the code, but it will give you an object to work with when API needs changes.
E.g
ApiModel::User
...
end
also,
if you wanting be bit more advance and thinking about versioning (if not you should ;) etc, I would recommend a gem like grape, because that handles lot of api related things and all you need to focus is the code.

I would create custom methods that contain custom DB queries that override ActiveRecord methods. Because model classes inherit from ActiveRecord, if you define a method in the model class with the same method name as the ActiveRecord method, then when you call it, the model class will be called. You can still call the ActiveRecord method if you call super
So, assuming you already setup the connection to the DB, you can do something like:
class User < ApplicationRecord
def self.find(id)
sql = "Select * from ... where id = ..."
records_array = ActiveRecord::Base.connection.execute(sql)
end
end
So when you call find it will call the User find, and not the ActiveRecord find. You can also define a custom method so that you don't override the ActiveRecord find:
class User < ApplicationRecord
def self.new_find(id)
sql = "Select * from ... where id = ..."
records_array = ActiveRecord::Base.connection.execute(sql)
end
end
Not sure if its the best approach but its an approach :)

Related

ActiveRecord: Best way to add a 'fake' model class?

In our Rails application, the Post resource can be made by either a User or an Admin.
Thus, we have an ActiveRecord model class called Post, with a belongs_to :author, polymorphic: true.
However, in certain conditions, the system itself is supposed to be able to create posts.
Therefore, I'm looking for a way to add e.g. System as author.
Obviously, there will only ever be one System, so it is not stored in the database.
Naïvely attempting to just add an instance (e.g. the singleton instance) of class System; end as author returns errors like NoMethodError: undefined method `primary_key' for System:Class.
What would be the cleanest way to solve this?
Is there a way to write a 'fake' ActiveRecord model that is not actually part of the database?
There's two ways that I see that make the most sense:
Option A: Add a 'system' Author record to the DB
This isn't a horrible idea, it just shifts the burden onto you making sure certain records are present in every environment. But you can always create these records in seed files if you want to ensure they're always created.
The benefit over option B is that you can just use standard ActiveRecord queries to find all of the system's Posts.
Option B: Leave the association nil and add a new flag for :created_by_system
This is what I would opt for. If a Post was made by the system, just leave the author reference blank and set a special flag to indicate this model was created internally.
You can still have a method to quickly get a list of all of them just by making a scope:
scope :from_system, -> { where(created_by_system: :true) }
Which one you choose I think depends on whether you want to be able to query Post.author and get information about the System. In that case you need to take option A. Otherwise, I would use option B. I'm sure there's some other ways to do it too but I think this makes the most sense.
Finally I ended up with creating the following 'fake' model class that does not require any changes to the database schema.
It which leverages a bit of meta-programming:
# For the cases in which the System itself needs to be given an identity.
# (such as when it does an action normally performed by a User or Admin, etc.)
class System
include ActiveModel::Model
class << self
# The most beautiful kind of meta-singleton
def class
self
end
def instance
self
end
# Calling`System.new` is a programmer mistake;
# they should use plain `System` instead.
private :new
def primary_key
:id
end
def id
1
end
def readonly?
true
end
def persisted?
true
end
def _read_attribute(attr)
return self.id if attr == :id
nil
end
def polymorphic_name
self.name
end
def destroyed?
false
end
def new_record?
false
end
end
end
Of main note here is that System is both its own class and its own instance.
This has the following advantages:
We can just pass Post.new(creator: System) rather than System.new or System.instance
There is at any point only one system.
We can define the class methods that ActiveRecord requires (polymorphic_name) on System itself rather than on Class.
Of course, whether you like this kind of metaprogramming or find it too convoluted is very subjective.
What is less subjective is that overriding ActiveRecord's _read_attribute is not nice; we are depending on an implementation detail of ActiveRecord. Unfortunately to my knowledge there is no public API exposed that could be used to do this more cleanly. (In our project, we have some specs in place to notify us immediately when ActiveRecord might change this.)

Rails 4 - Mapping a model dynamically on two different database tables

I have a multi domain app talking to a legacy database.
In that DB I have two tables with different names, lets call them USER_A and USER_B. Their structure and data types are exactly the same, the only difference is that they get their data from different domains.
Now, I would like to have a single scaffold (model/controller/view) that, depending on the domain, maps to the right DB table.
Domain A would work with a model/controller called User which maps internally to the db table USER_A, and Domain B would work with the same model/controller User but maps to the table USER_B.
I would also like to use resource :user in my routes to access the model the rails way.
So somehow I need to overwrite the model on initialization but I am not quite sure how to go about it.
How would one go about this using Rails ActiveRecord?
I don't have a multitable DB ready to test with, so this is an educated guess at the solution:
# respective models
class User < ActiveRecord::Base
end
class DomainAUser < User
self.table_name = "USER_A"
end
class DomainBUser < User
self.table_name = "USER_B"
end
# controller
def set_user
#user = if request.subdomain(0) == "DomainA"
DomainAUser.find(params[:id])
else
DomainBUser.find(params[:id])
end
end
Edit: Here's an alternative bit of metaprogramming hackery which does the subclass instantization within the parent class itself. Tested and working.
I really wouldn't want to maintain something like this though.
# model
class User < ActiveRecord::Base
def self.for_domain(domain_suffix)
class_eval "class DomainUser < User; self.table_name='user_#{domain_suffix}'; end"
"User::DomainUser".constantize
end
end
# controller
User.for_domain("a").new

Inject condition in every query in Rails

Some of my models has a column named "company_id".
I need that all querys in these models has a condition based in this column, so I can easily separate the companies rows.
Something like this:
Customer.where(state: x).`where(company_id: current_company)`...
How can I intercept this method to enforce this extra condition?
I would recommend using a concern to add this requirement as a default scope to all of your models.
module HasCompany
extend ActiveSupport::Concern
included do
default_scope { where(company_id: current_company) }
end
end
class Customer < ActiveRecord::Base
include HasCompany
...
end
Note: this approach will only work if you have access to current_company as a class method on your models.
Where does this code live? It looks like controller logic? If it's in a controller, then you can just set the current_company in a before_action in the application controller—probably like you're doing already. Presuming you have a has_many relationship between company and customers, you should just do current_company.customers.where(state: x).
If this code lives in a model, that's when things get tricky. You shouldn't have access to current_company in a model, since that deals with the current request.

Single Table Inheritance with Conditions

I have model User and model Recruiter. Currently, these are two separate tables, but I want to make them one.
Current:
User: id, username, password, name
Recruiter: id, user_id
Ideal:
User: id, username, password, role (recruiter, admin)
I understand the basics of STI. What I'm wondering is, when I perform methods on the new Recruiter controller (that inherits from User) how do I make sure all my methods are calling on users that are only a recruiter? Thus, queries along the lines of... SELECT * FROM users WHERE role = 'recruiter' for everything.
That is something rails takes care of for you, out of the box. You do not have to manually query on a particular type of user, just query on the right model.
I must also mention that by default rails assumes that your sti_column is called type, but can be overridden to role easily.
Let's admit you have your 2 classes:
class User < ActiveRecord::Base
end
class Recruiter < User
end
Rails will automagically add a type column in the users table so that in your controller, if you do something like this:
class RecruitersController < ApplicationController
def index
#recruiters = Recruiter.all
end
end
Rails will automatically fetch the records with type = 'Recruiter' and you don't even have to set this manually. If you do:
Recruiter.new(name: 'John').save
A new User will be created in database with the field type set to 'Recruiter'.
you would define your models something like this:
class User < ActiveRecord::Base
...
end
class Recruiter < User
...
def initialize
# ... special initialization for recruiters / you could put it here
super
end
...
end
and to create a new recruiter, you would do this:
Recruiter.create(:name => "John Smith")
and because of the type attribute in the STI user table (set to 'Recruiter'), the record will be for a recruiter.
You could put the special initialization for the STI models either in the model's initializer, or in a before filter with a if-cascade checking the type.
An initializer is probably much cleaner.
Have you tried has_one association?
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one

Allow Active Record Model to Assign Association using String

I have a fairly basic set of models where a 'Project' has one 'Owner'. I'd like to allow users to enter the owner's name when creating a new project. If owner doesn't exist, a new one should be created. Does a good way to do this exist? I've been thinking about using attr_accessor and before_validation however it seems it would conflict with the relationship. Any ideas? Thanks!
Use something different than the name of your relationship... owner_name should be fine. Then write the necessary before_validation method.
I'd use something along the lines of this in your controller:
def update
Project.transaction do
#project.owner = Owner.find_or_create_by_name(params[:project].delete(:owner_name))
#project.attributes = params[:project]
#project.save!
end
end

Resources