Active Record joined-grouped query on PostgreSQL - ruby-on-rails

I am working on a rails project for a while and I juste encountered (what I think is) a small problem.
To make it simple, I have 3 model like this:
class House < ActiveRecord::Base
has_many :house_style_relationships
end
class HouseStyleRelationship < ActiveRecord::Base
attr_accessible :percentage
belongs_to :style
belongs_to :house
end
class Style < ActiveRecord::Base
has_many :house_style_relationships
end
So the HouseStyleRelationship model simply let us know the style percentage of an House.
For example an House can be 70% Modern and 30% Classic.
What I need to do is to get the average of each styles for all the houses.
To do that I used this working query under SQLite:
houses_count = Houses.count
HouseStyleRelationship.joins(:style).select("style.name, SUM(house_style_relationships.percentage)/#{houses_count} as percentage").group("styles.id")
But when I decided to push all this stuff on Heroku, I got a problem with PostgreSQL.
In fact, to create an HouseStyleRelationship object (I don't even need), ActiveRecord ask for a HouseStyleRelationship.id which make the Group By crashing (with the query).
(PostgreSQL don't want to group records with different ids)
Do you have a solution to prevent ActiveRecord to generate model instances as answer and so remove the HouseStyleRelationship.id from the query ?
(I can't use SELECT DISTINCT since I need the SUM() calculation)
Thanks in advance !

You need to create your join table such that it has no primary key. The create_table statement in your migration should look something like this.
create_table :houses_style_relationships, :id => false do |t|

Related

Ruby multiple joins

Here are the models I created in my Rails app:
class Pet < ActiveRecord::Base
belongs_to :shelter
belongs_to :type
end
class Shelter < ActiveRecord::Base
has_many :pets
end
class Type < ActiveRecord::Base
has_many :pets
end
I'm trying to find shelters that don't have any exotic pets in them but am stuck joining the tables in the way where I can retrieve that information! Here is my latest attempt where I believe I'm at least reaching the Types table. Any help and explanation on joins would be much appreciated!
Shelter.joins(:pet => :type).where(:types => {exotic => false})
I believe it is impossible to get the results you want using just JOINS. Instead you need to find which shelters do have exotic pets and then negate that.
One way to accomplish that is through a subquery:
Shelter.where(<<~SQL)
NOT EXISTS (
SELECT 1 FROM pets
INNER JOIN types ON types.id = pets.type_id
WHERE shelters.id = pets.shelter_id
AND types.exotic IS TRUE
)
SQL
Of course that involves a lot of explicit SQL, something I don't mind, but others do not like it.
You can also do something similar using just the ActiveRecord query interface.
shelters_with_exotics = Shelter.joins(pets: :type).where(types: { exotic: true })
Shelter.where.not(id: shelters_with_exotics)
NOTE: The queries for the two examples are different. If it mattered you would need to benchmark both of them to determine which one performed best.

Should I use a LIKE query for these ActiveRecord relationships?

Let's say I have a single web page form user interface with 2 sets of checkboxes. With set 1 checkboxes, I can check off what Trainers I would like ("Jason", "Alexandra, etc.) With set 2 checkboxes, I can check off what animals I would like to see ("Tigers", "Bears", etc.) Once I submit the form with these options, I get back a list of zoos that match the criteria (let's assume all the trainers work at all the zoos and all the animals are at all the zoos for discussion's sake)
We'll be running our database query by "name" (e.g., search using trainer names and animal names, NOT database ids)
Let's say we are using a Postgres database that has hundreds of thousands of rows (if not millions).
Is it more efficient to search using an "ILIKE" query or is it better to do a standard join query (e.g., Zoo.includes(:animals, :trainers).where("animals.name = ? and trainers.name = ?", animal_names, trainer_names)?
Is there a better way than what I just showed in #1 above?
model setup
class Zoo < ActiveRecord::Base
has_many :animals, through: zoo_animals
has_many :trainers, through: zoo_trainers
has_many :zoo_trainers
has_many :zoo_animals
end
class Animal < ActiveRecord::Base
has_many :zoos, through :zoo_animals
has_many :zoo_animals
end
class Trainer < ActiveRecord::Base
has_many :zoos, through :zoo_trainers
has_many :zoo_trainers
end
class ZooAnimal < ActiveRecord::Base
belongs_to :animal
belongs_to :zoo
end
class ZooTrainer < ActiveRecord::Base
belongs_to :zoo
belongs_to :trainer
end
EDIT: let's suppose I don't have access to the database ID's.
LIKE '%Jason%' is much less efficient than querying for the exact string 'Jason' (or querying for an ID), because while exact comparisons and some uses of LIKE can use an index on the column being queried, LIKE with a pattern beginning with a wildcard can't use an index.
However, performance doesn't sound like the most important consideration here. LIKE %Jason% will still probably be fast enough on a reasonably sized database under reasonable load. If the application really needs to search for things by substring (which implies that a search might have multiple results), that requirement can't be met by simple equality.
There are an endless number of higher-powered solutions to searching text, including Postgres built-in full-text search and external solutions like Elasticsearch. Without specific requirements for scaling I'd go with LIKE until it started to slow down and only then invest in something more complicated.

Rails: Creating models from existing tables?

I have tables already created from a different project. Their names are formatted like aaa_bbb_ccc_ddd (all non plural and some parts aren't a convention word). I have successfully created a schema from the database by reading this. But now I have to make the actual models. I've looked at RMRE, but they enforce the ActiveRecord convention on my tables and change their names, which I don't want to do because other apps depend on those tables.
What is the best way to automatically create models and a schema from existing tables?
just a theory, not sure how this would work in real app:
create models named as ActiveRecord convention requires, for example for table aaa_bbb_ccc_ddd you'll create a model AaaBbb and map this model to your table:
class AaaBbb < ActiveRecord::Base
self.table_name = "aaa_bbb_ccc_ddd"
end
or a more human example:
class AdminUser < ActiveRecord::Base
self.table_name = "my_wonderfull_admin_users"
end
Now you'll have AaaBbb as resource in routes meaning you'll have a url like:
.../aaa_bbb/...
and if you want to use the table name name in url I guess you could rewrite the route:
get 'aaa_bbb_ccc_ddd/:id', "aaa_bbb#show", as: "aaa_bbb"
again, just a theory that might help you out. I haven't worked with such cases yet but would've start from this.
edit
to automate model creation from database:
https://github.com/bosko/rmre
but I think this will create models by rails convention with wierd names that you'll have to use as resource in your app.
A good template that I found on SO in case you want to use a model name different from table name:
class YourIdealModelName < ActiveRecord::Base
self.table_name = 'actual_table_name'
self.primary_key = 'ID'
belongs_to :other_ideal_model,
:foreign_key => 'foreign_key_on_other_table'
has_many :some_other_ideal_models,
:foreign_key => 'foreign_key_on_this_table',
:primary_key => 'primary_key_on_other_table'
end
Just switch from Rails to Django and make
your life happier and also make your work normal:
$ python manage.py inspectdb my_table_without_existing_model > some_new_model.py
That's enough. Two seconds of work :)

Rails Has Many Association

I have few question that bugs me off and need to be answered. Everything is related to the following tutorial Two Many-to-Many
Question 1
Does the join table using has_many need to have an id? or its best practice to remove the id? and add an index and using the two other primary key and set it unique and together?
Question 2
How can it be done in the migration of creating a table?
Question 3
After doing these relationship model and updating the data. I would like to create a new set of data everytime it is updated (to preserve the data). How would a controller would look in the update, new, create model?
Question 4
In the the middle table, I would like to set attributes such has a visible true, or false, how can I set also not just the third table but also the second table arguments
First ... a word of caution: That railscast is very old. There may be syntactical things in that episode that have been dated by new versions of rails.
Question 1
If you are using the has_many through method then you have to have an id column in the join model because you are using a full blown model. As Ryan mentions in the episode, you'll choose this method if you need to track additional information. If you use the has_and_belongs_to_many method, you will not have an id column in your table.
If you want to achieve a check where you do not allow duplicates in your many-to-many association (ie allow the pairing of item a with item b and again allowing another record of item a to item b), you can use a simple validates line with a scope:
validates_uniqueness_of :model_a_id, :scope => [:model_b_id]
Question 2
You can add indices in your migrations with this code
add_index :table_name, [ :join_a_id, :join_b_id ], :unique => true, :name => 'by_a_and_b'
This would be inserted into the change block below your create_table statement (but not in that create_table block). Check out this question for some more details: In a join table, what's the best workaround for Rails' absence of a composite key?
Question 3
I'm not completely clear on what you're looking to accomplish but if you want to take some action every time a new record is inserted into the join model I would use the after_create active record hook. That would look something like this.
class YourJoinModel < ActiveRecord::Base
after_create :do_something
def do_something
puts "hello world"
end
end
That function, do_something, will be called each time a new record is created.
Question 4
Using the has_many through method will give you access to the additional attributes that you defined in that model on both sides of the relationship. For example, if you have this setup:
class Factory < ActiveRecord::Base
has_many :widgets, :through => :showcases
end
class Widget < ActiveRecord::Base
has_many :factories, :through => :showcases
end
class Showcases < ActiveRecord::Base
belongs_to :factory
belongs_to :widget
attr_accessiable :factory_id, :widget_id, :visible
end
You could say something like
widget = Widget.first
shown = widget.showcases
shown.first.visible
or
shown = widget.showcases.where( :visible=> true )
You can also reach to the other association:
shown.first.factory
The reason for having an id column in an association is it gives you a way of deleting that specific association without concerning yourself with the relationship it has. Without that identifier, associations are hard to define outside of specifying all foreign keys.
For a trivial case where you have only two components to your key, this isn't that big a differentiator, but often you will have three or more as part of your unique constraint and there's where things get tricky.
Having an id also makes the relationship a first-class model. This can be useful when you're manipulating elements that have associated meta-data. It also means you can add meta-data effortlessly at a later date. This is what you mean by your "Question 4". Add those attributes to the join model.
Generally the join model is created like you would any other model. The primary key is the id and you create a series of secondary keys:
create_table :example_things |t|
t.integer :example_id
t.integer :thing_id
end
add_index :example_joins, [ :example_id, :thing_id ], :unique => true
add_index :example_joins, :thing_id
The main unique index serves to prevent duplication and allows lookups of key-pairs. The secondary serves as a way of extracting all example_id for a given thing_id.
The usual way to manipulate meta-data on the join model is to fetch those directly:
#example_things = #example.example_things.includes(:thing)
This loads both the ExampleThing and Thing models associated with an Example.

Model association changes in production environment, specifically converting a model to polymorphic?

I was hoping I could get feedback on major changes to how a model works in an app that is in production already.
In my case I have a model Record, that has_many PhoneNumbers.
Currently it is a typical has_many belongs_to association with a record having many PhoneNumbers.
Of course, I now have a feature of adding temporary, user generated records and these records will have PhoneNumbers too.
I 'could' just add the user_record_id to the PhoneNumber model, but wouldn't it be better for this to be a polymorphic association?
And if so, if you change how a model associates, how in the heck would I update the production database without breaking everything? >.<
Anyway, just looking for best practices in a situation like this.
Thanks!
There's two approaches that might help you with this.
One is to introduce an intermediate model which handles collections of phone numbers. This way your Record and UserRecord can both belong_to this collection model and from there phone numbers and other contact information can be associated. You end up with a relationship that looks like this:
class Record < ActiveRecord::Base
belongs_to :address_book
delegate :phone_numbers, :to => :address_book
end
class UserRecord < ActiveRecord::Base
belongs_to :address_book
delegate :phone_numbers, :to => :address_book
end
class AddressBook < ActiveRecord::Base
has_many :phone_numbers
end
This kind of re-working can be done with a migration and a bit of SQL to populate the columns in the address_books table based on what is already present in records.
The alternative is to make UserRecord an STI derived type of Record so you don't need to deal with two different tables when defining the associations.
class Record < ActiveRecord::Base
has_many :phone_numbers
end
class UserRecord < Record
end
Normally all you need to do is introduce a 'type' string column into your schema and you can use STI. If UserRecord entries are supposed to expire after a certain time, it is easy to scope their removal using something like:
UserRecord.destroy_all([ 'created_at<=?', 7.days.ago ])
Using the STI approach you will have to be careful to scope your selects so that you are retrieving only permanent or temporary records depending on what you're intending to do. As UserRecord is derived from Record you will find they get loaded as well during default loads such as:
#records = Record.find(:all)
If this causes a problem, you can always use Record as an abstract base class and make a derived PermanentRecord class to fix this:
class PermanentRecord < Record
end
Update during your migration using something like:
add_column :records, :type, :string
execute "UPDATE records SET type='PermanentRecord'"
Then you can use PermanentRecord in place of Record for all your existing code and it should not retrieve UserRecord entries inadvertently.
Maintenance page is your answer.
Generate migration which updates table structure and updates existing data. If you're against data updates in migrations - use rake task.
Disable web access (create maintenance page)
Deploy new code
Run pending migrations
Update data
Enable web access (remove maintenance page).

Resources