ActiveRecord: Read Only Model based on Query - ruby-on-rails

I have worked with Postgres views in the past to model derived representation of data.
Overwriting ActiveRecord's readonly? helps with implementing that.
Unfortunately when used to heavily views have the downside of making schema changes very hard.
So I wonder if there is a way to assign an arbitrary query to a model and treat it like a table?
EDIT: Sorry I should have been more clear:
What I'd like is to do something like "Give me all users left joined with their comments" through a model called "UserWithComments". I know that this particular example could easily be achieved by using ActiveRecords DSL via User.includes(:comments) or similar.
If the query gets more complex the ActiveRecord DSL hits its limits so I could just create a view with my SQL and then use a readonly model that references the view. There are circumstances where creating a view is not feasible so I was wondering way to have a model called UserWithComments which is not linked to a table or view but instead has some kind of config option with an SQL like select <attributes> from users u left join comments c on c.user_id = u.id

Your question is a bit vague but I have used views this way:
Create your view in the DB. I am using an example called gear_alerts. Then in Rails create a model called gear_alert.rb:
class GearAlert < ApplicationRecord
self.primary_key = :id
belongs_to :category
belongs_to :user
has_many :user_tags, through: :user
scope :active, -> { where(active: true) }
scope :deactivated, -> {where(active: false) }
def readonly?
true
end
end
as you can see, since views present themselves as tables normal ActiveRecord stores, you can treat it as such in a read-only context. I like views for when I am using complex table joins that have to be manipulated heavily before going to the presentation layer. Leaving it all for the browser to render can really slow things down. Maintaining views can be a pain. So I highly recommend the Scenic gem https://github.com/scenic-views/scenic as it lets you easily modify views from the rails migration tool while also giving you history of how the view has been modified over time.

Related

Can I eager load attributes from one-to-one relationship for delegation without instantiating the related model?

Imagine this:
class House < ActiveRecord::Base
belongs_to :ground
delegate :elevation_in_meters, to: :ground
# attributes: stories, roof_type
end
class Ground < ActiveRecord::Base
has_one :house
# attributes: elevation_in_meters, geo_data
end
Then to eager load ground so that house.elevation_in_meters can be called without loading Ground I can do:
houses=House.includes(:ground).first(3)
The problem with this is, that the entire Ground object is actually instantiated with all attributes including the geo_data attribute - which I don't need in this case. The reason why I care is, that the query needs to be VERY performant, and geo_data is a pretty huge text field. I only need to read the delegated attributes, not write to them.
What approach could I take on eager loading the elevation_in_meters attribute from Ground without loading everything from Ground?
I'm on rails 4.1 btw
NOTE: Preferably I would like to have this eager loading behaviour by default for House, so that I do not need to specify it every time.
First off write a scope for the model you want to partially get and select the fields you like. Notice that I used the full name (with table name) and a string for the select. I'm not sure if you could just select(:elevation_in_meters,:geo_data) since I've copied this from our production example, and we use some joins with this scope that wont work without the table name. Just try it yourself.
class Ground < ActiveRecord::Base
has_one :house
attributes: elevation_in_meters, geo_data
scope :reduced, -> {
select('grounds.elevation_in_meters, grounds.geo_data')
}
end
With the scope present you can make a second belongs_to relation (don't be scared that it messes up your first one, since rails relations are basically just methods that are created for you), that calls the scope on your Ground model.
class House < ActiveRecord::Base
belongs_to :ground
belongs_to :ground_reduced, ->(_o) { reduced },
class_name: 'Ground', foreign_key: 'ground_id'
delegate :elevation_in_meters, to: :ground_reduced
# go for an additional delegation if
# you also need this with the full object sometimes
end
In the end you can just call your query like this:
houses = House.includes(:ground_reduced).first(3)
Technically it is not the proper answer to your question, since the Ground object is still instantiated. But the instance will only have the data you wanted and the other fields will be nil, so it should do the trick.
UPDATE:
As I just saw that you want to preferably have this behaviour as default, just add a scope for your House:
scope :reduced, -> { includes(:ground_reduced) }
You could then add this as your default scope, since your original relation will be untouched by this.
I know it's been a while but I just stumbled across this.
If you're only interested in the singular attribute you can also use a joins combined with a select and the attribute will magically be added to your House instance.
res = House.joins(:ground).select('houses.*, grounds.elevation_in_meters').first
res.elevation_in_meters # attribute is available on the object
To always have this attribute present, make it the default_scope for House, like so:
default_scope { joins(:ground).select('houses.*, grounds.elevation_in_meters') }
Depending on the nature of the tables you're joining you may need a distinct also.

Rails have ActiveRecord grab more than one association in one go?

The question below had a good answer to grab associated values of an activerecord collection in one hit using Comment.includes(:user). What about when you have multiple associations that you want to grab in one go?
Rails have activerecord grab all needed associations in one go?
Is the best way to just chain these together like below Customer.includes(:user).includes(:sales).includes(:prices) or is there a cleaner way.
Furthermore, when I am doing this on a loop on an index table. Can I add a method on the customer.rb model so that I can call #customers.table_includes etc and have
def table_includes
self.includes(:user).includes(:sales).includes(:prices)
end
For the record I tested the above and it didn't work because its a method on a collection (yet to figure out how to do this).
In answering this, I'm assuming that user, sales, and prices are all associations off of Customer.
Instead of chaining, you can do something like this:
Customer.includes(:user, :sales, :prices)
In terms of creating an abstraction for this, you do have a couple options.
First, you could create a scope:
class Customer < ActiveRecord::Base
scope :table_includes, -> { includes(:user, :sales, :prices) }
end
Or if you want for it to be a method, you should consider making it a class-level method instead of an instance-level one:
def self.table_includes
self.includes(:user, :sales, :prices)
end
I would consider the purpose of creating this abstraction though. A very generic name like table_includes will likely not be very friendly over the long term.

Converting multiple existing classes to inherit from newly created class in rails

At the moment in my rails app I have a few classes that are different products.
e.g. one example is Circuits.
What I want to do is create a new class named Service and have all the individual product models inherit from it.
Previously my circuit.rb model was
class Circuit < ActiveRecord::Base
but now it is
class Circuit < Service
and I have created a new `Services1 class, simply:
class Service < ActiveRecord::Base
end
In my circuit_controller.rb I have a few functions, the most straightforward being list
def list
conditions = []
conditions = ["organisation_id = ?", params[:id]] if params[:id]
#circuits = Circuit.paginate(:all, :page => params[:page], :conditions => conditions, :per_page => 40)
end
but changing the circuit model to inherit from services results in my circuit list view being empty which I didn't expect.
In my services table I have included a type field for storing which type of product it is but at the moment the table is empty.
Is multi table inheritance the best way to go? The app is quite large so I don't want to have to refactor a lot of code to implement this change.
Single Table inheritance would definitely be a no-go so I am wondering if some kind of association would be better.
update
Just tried following this blog post:
http://rhnh.net/2010/08/15/class-table-inheritance-and-eager-loading
so I have added
belongs_to :service
to my individual product models and then in the services model
SUBCLASSES = [:circuit, :domain]
SUBCLASSES.each do |class_name|
has_one class_name
end
end
then in the service_controller.rb
def list
#services = Service.all(:include => Service::SUBCLASSES)
end
finally, in the list view I try to inspect and debug the #services variable but it's empty because the query is running on an empty services table, should it not be also running on the circuits and domains tables aswell?
My take : I guess even if you are going to use multi table inheritance you will need to (ideally) write a task to put in all the common variables into your Service table.
Multi table inheritance doesn't serve its purpose if the common variables are not in the parent table.
Plus it also depends on how many uncommon attributes are there in your existing models. If it is none I would rather use STI.If the structure of the tables are same i would rather use STI.
But i am assuming its is not the same for all the models.

How to save related Models in one transaction?

I have two models:
class Customer < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :customer
validates :customer, presence: true
end
Then, in my controller, I would expect to be able to create both in
"one" sweep:
#customer = Customer.new
#customer.contacts.build
#customer.save
This, fails (unfortunately translations are on, It translates to
something like: Contact: customer cannot be blank.)
#customer.errors.messages #=> :contacts=>["translation missing: en.activerecord.errors.models.customer.attributes.contacts.invalid"]}
When inspecting the models, indeed, #customer.contacts.first.customer
is nil. Which, somehow, makes sense, since the #customer has not
been saved, and thus has no id.
How can I build such associated models, then save/create them, so that:
No models are persisted if one is invalid,
the errors can be read out in one list, rather then combining the
error-messages from all the models,
and keep my code concise?
From rails api doc
If you are going to modify the association (rather than just read from it), then it is a good idea to set the :inverse_of option on the source association on the join model. This allows associated records to be built which will automatically create the appropriate join model records when they are saved. (See the ‘Association Join Models’ section above.)
So simply add :inverse_of to relationship declaration (has_many, belongs_to etc) will make active_record save models in the right order.
The first thing that came to my mind - just get rid of that validation.
Second thing that came to mind - save the customer first and them build the contact.
Third thing: use :inverse_of when you declare the relationship. Might help as well.
You can save newly created related models in a single database transaction but not with a single call to save method. Some ORMs (e.g. LINQToSQL and Entity Framework) can do it but ActiveRecord can't. Just use ActiveRecord::Base.transaction method to make sure that either both models are saved or none of them. More about ActiveRecord and transactions here http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html

How many classes is too many? Rails STI

I am working on a very large Rails application. We initially did not use much inheritance, but we have had some eye opening experiences from a consultant and are looking to refactor some of our models.
We have the following pattern a lot in our application:
class Project < ActiveRecord::Base
has_many :graph_settings
end
class GraphType < ActiveRecord::Base
has_many :graph_settings
#graph type specific settings (units, labels, etc) stored in DB and very infrequently updated.
end
class GraphSetting < ActiveRecord::Base
belongs_to :graph_type
belongs_to :project
# Project implementation of graph type specific settings (y_min, y_max) also stored in db.
end
This also results in a ton of conditionals in views, helpers and in the GraphSetting model itself. None of this is good.
A simple refactor where we get rid of GraphType in favor of using a structure more like this:
class Graph < ActiveRecord::Base
belongs_to :project
# Generic methods and settings
end
class SpecificGraph < Graph
# Default methods and settings hard coded
# Project implementation specific details stored in db.
end
Now this makes perfect sense to me, eases testing, removes conditionals, and makes later internationalization easier. However we only have 15 to 30 graphs.
We have a very similar model (to complicated to use as an example) with close to probably 100 different 'types', and could potentially double that. They would all have relationships and methods they inheritated, some would need to override more methods then others. It seems like the perfect use, but that many just seems like a lot.
Is 200 STI classes to many? Is there another pattern we should look at?
Thanks for any wisdom and I will answer any questions.
If the differences are just in the behavior of the class, then I assume it shouldn't be a problem, and this is a good candidate for STI. (Mind you, I've never tried this with so many subclasses.)
But, if your 200 STI classes each have some unique attributes, you would need a lot of extra database columns in the master table which would be NULL, 99.5% of the time. This could be very inefficient.
To create something like "multiple table inheritance", what I've done before with success was to use a little metaprogramming to associate other tables for the details unique to each class:
class SpecificGraph < Graph
include SpecificGraphDetail::MTI
end
class SpecificGraphDetail < ActiveRecord::Base
module MTI
def self.included(base)
base.class_eval do
has_one :specific_graph_detail, :foreign_key => 'graph_id', :dependent => :destroy
delegate :extra_column, :extra_column=, :to => :specific_graph_detail
end
end
end
end
The delegation means you can access the associated detail fields as if they were directly on the model instead of going through the specific_graph_detail association, and for all intents and purposes it "looks" like these are just extra columns.
You have to trade off the situations where you need to join these extra detail tables against just having the extra columns in the master table. That will decide whether to use STI or a solution using associated tables, such as my solution above.

Resources