Converting multiple existing classes to inherit from newly created class in rails - ruby-on-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.

Related

ActiveRecord: Read Only Model based on Query

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.

Defining attributes at runtime based on data from related object

I'm building an application where users are part of an Organisation. An organisation has many Lists, which in turn have many ListItems.
Now, I would like for admin users to be able to specify which attributes are available on list items, based on the organisation they belong to (or rather, on the organisation their list belongs to), without having to touch any code.
So far, when defining attributes that are not bound to a specific column in the database, I have used document_serializable, a nifty little gem (based on virtus) which serializes virtual attributes to a JSONB column in the db. I like this approach, because I get all of virtus' goodies (types, coercion, validations, etc.), and because data ends up sitting in a JSONB column, meaning it can be loaded quickly, indexed, and searched through with relative ease.
I would like to keep using this approach when adding these user-defined attributes on the fly. So I'd like to do something like:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
organisation.list_attributes.each do |a, t|
attribute a, t
end
end
Where Organisation#list_attributes returns the user-defined hash of attribute names and their associated types, which, for example, might look like:
{
name: String,
age: Integer
}
As you might have guessed, this does not work, because organisation.list_attributes.each actually runs in the context of ListItem, which is an instance of Class, and Class doesn't have an #organisation method. I hope that's worded in a way that makes sense1.
I've tried using after_initialize, but at that point in the object's lifecycle, #attribute is owned by ActiveRecord::AttributeMethods::Read and not DocumentSerializable::ClassMethods, so it's an entirely different method and I can't figure out wether I can still access the one I need, and wether that would even work.
Another alternative would be to find the organisation in question in some explicit way, Organisation#find-style, but I honestly don't know where I should store the information necessary to do so.
So, my question: at the moment of instantiating (initializing or loading2) a record, is there a way I can retrieve a hash stored in a database column of one of its relations? Or am I trying to build this in a completely misguided way, and if so, how else should I go about it?
1 To clarify, if I were to use the hash directly like so:
class ListItem < ApplicationRecord
belongs_to :list
delegate :organisation, to: :list
{
name: String,
age: Integer
}.each do |a, t|
attribute a, t
end
end
it would work, my issue is solely with getting a record's relation at this earlier point in time.
2 My understanding is that Rails runs a model's code whenever a record of that type is created or loaded from the database, meaning the virtual attributes are defined anew every time this happens, which is why I'm asking how to do this in both cases.
at the moment of instantiating (initializing or loading) a record, is
there a way I can retrieve a hash stored in a database column of one
of its relations?
Yes. This is fairly trivial as long as your relations are setup correctly / simply. Lets say we have these three models:
class ListItem < ApplicationRecord
belongs_to :list
end
class List < ApplicationRecord
belongs_to :organisation
has_many :list_items
end
class Organisation < ApplicationRecord
has_many :lists
end
We can instantiate a ListItem and then retrieve data from anyone of its parents.
#list_item = ListItem.find(5) # assume that the proper inherited
foreign_keys exist for this and
its parent
#list = #list_item.list
#hash = #list.organisation.special_hash_of_org
And if we wanted to do this at every instance of a ListItem, we can use Active Record Callbacks like this:
class ListItem < ApplicationRecord
belongs_to :list
# this is called on ListItem.new and whenever we pull from our DB
after_initialize do |list_item|
puts "You have initialized a ListItem!"
list = list_item.list
hash = list.organisation.special_hash_of_org
end
end
But after_initialize feels like a strange usage for this kind of thing. Maybe a helper method would be a better option!

How can you create an ActiveRecord collection from two separate tables without STI?

We have two models that belong to company: customer and vendor invoices. Currently they have their own index pages; generating a collection for pagination/sorting on these pages is as easy as current_company.customer_invoices.
class CustomerInvoice < ApplicationRecord
belongs_to :company
end
class VendorInvoice < ApplicationRecord
belongs_to :company
end
class Company < ApplicationRecord
has_many :customer_invoices, -> { order(due_date: :desc) }
has_many :vendor_invoices, -> { order(due_date: :desc) }
end
We now need to make a shared index page that will paginate and sort both kinds of invoices. STI seems like the obvious solution, but since they are functionally VERY different, and have minimal intersection in their schema columns, it strikes us as a bad use case. Is there any other option besides loading all of the records and sorting/paginating them in memory?
class Company
def invoices
(customer_invoices + vendor_invoices)
end
end
class InvoicesController < ApplicationController
def index
#invoices = current_company
.invoices
.sort_by(&:due_date)
.page(params[:whaveter_page])
end
end
As the number of invoices grows this will have super terrible memory performance :( .
To give you a best answer, one would need to know about the structure of your database, use cases etc.
But here are two approaches that you can consider:
Create separate table that will store data for both models. Such table should only contain columns required for filtering, sorting and data displayed on the combined list. and of course references to original rows in respectable tables. With such table it is pretty straightforward, you just query this table. Major disadvantage is that now you need to write to two tables when creating or updating invoice. But generating combined index will be super-fast. Technically you can move all the indexes to this table only and do other database optimizations.
You can use UNION statement and combine results from both tables into one. You need to select similar columns from both tables with same types. It will be slower than the first solution, but the advantage is that you don't have to maintain additional table. It will be created on demand. Writing union statement with active record may be a little bit challenging

Rails 4.0 multiple models and one controller

I'm in the process of learning Ruby on Rails, and now I have created the mobile version of my application.
I created the relation between models ans controller is one-one. Now I want to make changes to manage three models from one controller. I have read and watch videos a lot about how to do this but, it doesn't work when I try to do it in my application.
Models:
class Subject < ActiveRecord::Base
has_many :pages
class Page < ActiveRecord::Base
belongs_to :subject
has_many :sections
class Section < ActiveRecord::Base
belongs_to :page
Controller:
class SubjectsController < ApplicationController
has_mobile_fu
layout "admin"
before_action :confirm_logged_in
def index
#subjects = Subject.newest_first
#pages = #subjects.pages.sorted
end
This is the error:
NoMethodError (undefined method pages' for # <ActiveRecord::Relation::ActiveRecord_Relation_Subject:0x007fbbf3c9b218>):
app/controllers/subjects_controller.rb:10:inindex'
The application works well if I keep each model managed by its controller. The problem started now that I want to control multiple models from one controller.
Can definitely use multiple models in a single controller. The issue here is you're calling a method that doesnt exist for the active record relation.
An active record relation is typically a collection of returned objects from a query using active record. So the newest_first is returning multiple, not just one. If you want to get all pages for the subjects and sort them, you can do this:
#subjects = Subject.newest_first
#pages = #subjects.map(&:pages).flatten.sort { |a, b| a.title <=> b.title }
Can switch the attribute on which you wish to sort by. The map function goes through each one, and returns the object of which i passed in the symbol. It's a shortcut for:
#subjects.map { |subject| subject.pages }
The flatten then takes that array of active record relations and flattens it into a single array. I then just use the array sort.
Edit Here's a way you can do it using the database:
#subjects = Subject.newest_first
#pages = Page.where.not(:subject_id => nil).order(:title)
MVC
Something else you'll benefit from is to look at the MVC Programming Pattern:
Rails is famous for its strict coherence to the Model-View-Controller pattern, as it works like this:
You send a request to your app
Rails "routes" your request to a specific controller / action
The controller will then collate data from your Models
The controller will then render a view to display this data
The relationship between models and controllers is exclusive; meaning you don't have to call certain models from a controller, etc.
So the basic answer is no, you don't need to call a single model from a controller. However, you do need to ensure you have the correct model associations set up, as per the explanation below:
Associations
The caveat here, is that since Ruby is object-orientated (and Rails, by virtue of being built on Ruby, also being so), it's generally considered best practice to build your application around objects
"Objects" are basically elaborate variables (constructed from your Model classes), but the pattern behind making OOP work properly is super important - everything from Rails' routes to your controller actions are designed to be object-ORIENTATED
Each time you initiate an instance of a Model, Rails is actually building an object for you to use. This object allows you to call / use a series of attributes / methods for the object, allowing you to create the experience you require with Rails
--
The bottom line -
I would highly recommend examining the ActiveRecord Associations in your models (which will determine whether you need to call a single model or not):
#app/controllers/subjects_controller.rb
Class SubjectsController < ApplicationController
def index
#subjects = Subject.newest_first #-> good use of OOP
#posts = # this is where your error occurs (`.posts` is only an attribute of each `Subject` object instance, which is fixed using the accepted answer)
end
end
Hopefully this gives you some more ideas about how to construct Rails applications

Correct way to create or update with multiple belongs_to in Rails

New to Rails and Ruby and trying to do things correctly.
Here are my models. Everything works fine, but I want to do things the "right" way so to speak.
I have an import process that takes a CSV and tries to either create a new record or update an existing one.
So the process is 1.) parse csv row 2.) find or create record 3.) save record
I have this working perfectly, but the code seems like it could be improved. If ParcelType wasn't involved it would be fine, since I'm creating/retrieving a parcel FROM the Manufacturer, that foreign key is pre-populated for me. But the ParcelType isn't. Anyway to have both Type and Manufacturer pre-populated since I'm using them both in the search?
CSV row can have multiple manufacturers per row (results in 2 almost identical rows, just with diff mfr_id) so that's what the .each is about
manufacturer_id.split(";").each do |mfr_string|
mfr = Manufacturer.find_by_name(mfr_string)
# If it's a mfr we don't care about, don't put it in the db
next if mfr.nil?
# Unique parcel is defined by it's manufacturer, it's type, it's model number, and it's reference_number
parcel = mfr.parcels.of_type('FR').find_or_initialize_by_model_number_and_reference_number(attributes[:model_number], attributes[:reference_number])
parcel.assign_attributes(attributes)
# this line in particular is a bummer. if it finds a parcel and I'm updating, this line is superfulous, only necessary when it's a new parcel
parcel.parcel_type = ParcelType.find_by_code('FR')
parcel.save!
end
class Parcel < ActiveRecord::Base
belongs_to :parcel_type
belongs_to :manufacturer
def self.of_type(type)
joins(:parcel_type).where(:parcel_types => {:code => type.upcase}).readonly(false) unless type.nil?
end
end
class Manufacturer < ActiveRecord::Base
has_many :parcels
end
class ParcelType < ActiveRecord::Base
has_many :parcels
end
It sounds like the new_record? method is what you're looking for.
new_record?() public
Returns true if this object hasn’t been saved yet — that is, a record
for the object doesn’t exist yet; otherwise, returns false.
The following will only execute if the parcel object is indeed a new record:
parcel.parcel_type = ParcelType.find_by_code('FR') if parcel.new_record?
What about 'find_or_create'?
I have wanted to use this from a long time, check these links.
Usage:
http://rubyquicktips.com/post/344181578/find-or-create-an-object-in-one-command
Several attributes:
Rails find_or_create by more than one attribute?
Extra:
How can I pass multiple attributes to find_or_create_by in Rails 3?

Resources