In Rails, can I order a query by a delegate method? - ruby-on-rails

I'm having difficulty ordering a query by a delegate method. I've been tasked with helping upgrade a fairly large Rails 3 application to Rails 4. I've come across this query in an index action. I am aware the naming of these objects is horrible and confusing.
# measurements_controller.rb
def index
#measurements = Measurement.includes(:identifier).order(:name)
end
In Rails 4, I'm getting this error:
ERROR: column info_items.name does not exist LINE 1: ...D (info_item_identifiers.name LIKE '%') ORDER BY "info_item...
So I took a look at the models and found:
# measurement.rb
class Measurement < InfoItem
...
end
# info_item.rb
belongs_to :identifier, class_name: 'InfoItemIdentifier'
delegate :name, :name=, to: :identifier
# info_item_identifier.rb
# This object has a name column in the database
If I'm in the terminal and I have an instance of a Measurement, I can easily call the .name method and it works great. But when it comes to .order(:name), that does not work due to the column not existing.
I have found one solution, but it seems to defeat the purpose of the delegate method. In the controller, I can change it to:
#measurements = Measurement.includes(:identifier).order('info_item_identifiers.name')
Since I know that InfoItem delegates the name method to InfoItemIdentifiers, I don't think my controller needs to know where it's delegated to.
Is there a way to keep the existing code and still order by the delegate method?

Yes, but it requires instantiating all your records first (which will decrease performance).
#measurements = Measurement.joins(:identifier).sort_by &:name
This code loads all Measurements and instantiates them and then sorts them by the Ruby method .name
Explanation
delegate only affects instances of your ActiveRecord model. It does not affect your SQL queries.
This line maps directly to a SQL query.
Measurement.includes(:identifier).order(:name)
As you have noticed, it looks for a name column on the measurements table and doesn't find anything. Your ActiveRecord model instances know that name only exists on identifier, but your SQL database doesn't know that, so you have to tell it explicitly on which table to find the column.

Related

Extracting data from an Activerecord with joined tables

Project.joins(:project_status).where(id: 1).first
I want to use a string such as "Project.ProjectStatus.name" and it will return the name of the joined project status, and simply something like Project.name.
.read_attribute() doesn't seem to go deeper to joined/included records. .send works when I do .send('ProjectStatus').send('id') but this does not seem ideal and could be dangerous, these variables come from users.
It's for a templating engine so I might have something like..
"{{Project.name}} status was changed to {{ProjectStatus.name}}"
Are there any gems I can pick up? I'm really exhausting what I've been searching for in Google now. In CakePHP I have used Hash::extract before which is a little like xpath.
What is the association between Project and ProjectStatus here? I assume it's has_one: :project_status?
Anyway, if you want to be able to call #project.name ( where #project is an instance of the class Project; .name should be specific to each individual project, so you should define it as an instance method rather than the class method Project.name), you can create a method in Project model:
def name
project_status.name
end
Assuming that you previously defined the association in model as
has_one: :project_status
Now the name can be retrieved simply by using .name

ActiveRecord Loading Relational Table As Enum Instances On Class Load

I have a relational database table that holds a product lookup. This table powers multiple systems, only one of which is a Rails app. In the Rails app, I want to use the product lookup as an ActiveRecord class with instance members with the product code - for example, the key code field is a 4-digit alphanumeric. It would be nice to be able to refer to instances by the code like this: ProductCode.01A3. I don't want to simply declare them in the Rails code, of course, because the DB is the system of record for multiple systems. Also, how would Ruby react to a non-existent product code? If ProductCode.ABCD doesn't exist, does it just silently return a nil, and I'd need nil checks everywhere? And then there's the issue of releasing a new ProductCode into production. Updating the table would require reloading the class instance variables.
Thoughts? Can this be done? Should this be done? I've searched for a library but maybe my Google-fu isn't that good.
Lookup tables are a great tool to reduce the number of database queries and I often use them in long worker processes. I wouldn't recommend using them in a webapp unless the data is often accessed rarely changes and is expensive to get.
My implementation for you problem would look like this:
class ProductCode
#product_codes = {}
class << self
attr_accessor :product_codes
end
def self.get(code)
#product_codes[code.to_s]
end
def self.cache_all
# whatever you do here
#product_codes = {'01A3' => 42}
end
end
ProductCodes.cache_all
ProductCodes.get('01A3')

Rails single table inheritance with lowercase type names

I'm working up an app that interfaces with a legacy database which has a type column used for single table inheritance. This database is still used by an existing PHP application so I have to work with what is there. I'm trying to set up some models for this and one of the key tables is set up with an STI scheme and a type column, however, all of the types in that column are entirely lowercase.
In my testing so far, rails works fine with the type column if I change the value to match the class name (for example, Claimant instead of claimant). However I don't want to go changing those values in the production database even though it would probably be ok, nor do I want to have to go in and modify the legacy app to save the names differently...
In order to fix this I have two questions...
1) Is there anyway I can configure the model to recognize that type = 'claimant' maps to class = 'Claimant'?
2) failing that, is there a way I can tell rails to not use STI on this table even though it has a type column?
I've done some googling and haven't come up with much yet...
I haven't tried this in an STI setting, but when using a legacy table I was able to use the method "set_table_name" on the ActiveRecord model.
class Claimant < ActiveRecord::Base
set_table_name 'claimant'
end
Or with a custom method:
def table_name
'claimant'
end
Apologies I haven't got an STI table handy to test this on, but thought it might help solve your problem.
In answer to the second part of your question, I believe you can disable Rails looking at the type column, by just specifying a non-existant column name.
class Claimant < ActiveRecord::Base
inheritance_column = :_type_column_disabled
end

Dealing with legacy database views in rails

I am new to ruby and rails and I am having difficulty conceptualizing the MVC techniques in conjunction with database views. I am dealing with a legacy database that has several viiews that are used to generate reports.
Where I get lost is how do I actually use a database view. Should it be put in a model? If so what exactly would that look like?
As an example the legacy db has a view called qryTranscriptByGroup. It is used in the legacy application in an SQL statement such as "SELECT * FROM qryTranscriptByGroup WHERE group='test_group'". This returns a small number of records usually less than 100.
If i create a model, Transcript, how would I define a method like Transcript.find_by_group(group)? As well, it would seem that I might need to prevent any other "find" methods as they would be invalid in this context.
There is also the the fact that the view is read-only and I would need to prevent any attempts to create, update or destroy it.
Perhaps I am going about this entirely the wrong way. The bottom line is that I need to get information from several tables (models?) that represent the information about a user (a transcript). Actually one or more users (transcripts plural).
-Thanks!
You can use a database view like a normal model.
In your case:
class Transcript < ActiveRecord::Base
set_table_name "qryTranscriptByGroup"
set_primary_key "if_not_id"
end
The query will be then:
Trascript.find_by_group('test_group')
without you need to declare anything.
Rails uses the method_missing method to magically generate find_by_column_name methods.
For the create/update/delete action you can simply delete them or not create them in the controller.

Overriding or aliasing name of column in legacy database using Rails/ActiveRecord

I'm writing a Rails application against a legacy database. One of the tables in this legacy database has a column named object_id. Unfortunately object_id is also an attribute of every object in Ruby, so when ActiveRecord is trying to use these objects to formulate a query it is using the Ruby defined object_id, rather than the value that is in the database.
The legacy application is immense at well over a million lines of code, so simply changing the name of the column in the database would be an option of last resort.
Questions:
1. Is there any way to make ActiveRecord/Rails use an alias or synonym for this column?
2. Is there any way in Ruby to make the object_id method behave differently, depending on who is calling it?
3. Can I simply override the behavior of the object_id method in my model (I assume this is a terrible idea, but had to ask)
Any suggestions are greatly appreciated.
I'm just kind of spitballing here, but you might try something like this:
class Legacy < ActiveRecord::Base
#... all the other stuff
#give yourself a way to access the DB version of object_id
def oid
attributes[:object_id]
end
def oid=(val)
attributes[:object_id]=val
end
#restore ruby's default #object_id implementation
def object_id
super
end
end
Check out alias_attribute http://www.railstips.org/blog/archives/2008/06/20/alias-attribute/ I believe that it does what you are looking for.

Resources