join table creating unintentional NULL entries in rails - sql

I got the following models. The join table references the user_id and the course_id and the combination is set unique. However, after testing my page on the server, e.g. leaving and joining courses many times,also with another current_user my join table created records where the course_id was empty. This got me errors in fetching data afterwards. So now I tried adding :false => null. Could this help? thx for your time
def self.up
create_table :course_enrollments do |t|
t.references :user, :null => false
t.references :course, :null => false
t.timestamps
end
add_index :course_enrollments, [:user_id, :course_id], :unique => true
end

Adding :null => false will cause an exception when you try to save a CourseEnrollment with a null ID. That's fine if you write the controller action to handle the exception. You could (and should) add validates_presence_of :user_id, :course_id in the CourseEnrollment model, so that instances with null IDs will not be valid, and can be handled with the normal <model>.save method.
However the bigger question is why does your app save rows with null IDs in the first place? For example, you might be creating orphaned rows (by deleting the associated Course or User). Generally it's a good idea to add :dependent => :destroy on has_many associations to prevent this.

Related

What is active record doing on the sql side in a polymorphic association?

I understand what is happening on the ruby level but when a model is represented as a foreign key on its own table, as in "a comment can have many comments" are new sql tables beign created to represent those comments under the hood? I apologize if my question is unclear. Any and all answers are much appreciated.
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :content, {null: false}
t.integer :commentable_id, {null: false}
t.string :commentable_type, {null: false}
t.references :commenter, null: false
t.timestamps(null: false)
end
end
end
Firstly in your migration you can use polymorphic: true to create the polymorphic references:
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :content, {null: false}
t.references :commentable, polymorphic: true, index: true
t.references :commenter, null: false
t.timestamps(null: false)
end
end
end
As you have already determined this will simply generate the :commentable_id and :commentable_type columns in your comments table that represent the polymorphic association - no special tables or other magic going on.
The commentable_type column is simply a string that stores the name of the model that the row belongs_to. The commentable_id is naturally an integer that holds the id of the model instance associated with this polymorphic association.
However, if you really want to find out whats going on under the hood just connect to your database and look at what you have.
Some useful commands to get you started (assuming you have connected to your database using psql) :
\dt (equivalent to SHOW TABLES)
\d+ tablename (equivalent to DESC tablename - where tablename is the name of the table you want information on)
Active Record uses the commentable_type column to constantize it and find the associated record by the commentable_id:
i.e.
commentable_type = "Post" # for example
commentable_type.constantize # returns the Post class model
commentable_type.constantize.find commentable_id # find the Post record
# the above line is equivalent to:
Post.find commentable_id

Rails 4 has_many associations w/Postgres: query table A for all records that actually have B, C, and D

Given a model with multiple has_many associations like this:
class Route < ActiveRecord::Base
has_many :flights
has_many :deals
has_many :ratings
...
end
Given a scenario where not all routes actually have all 3, is there an easy way to find the number of routes that do have all 3?
Based on other SO questions I have tried this:
scope :active, -> { joins(:ratings).joins(:deals).joins(:flights). Then I call Route.active.count in my console but the process just hangs. These tables are pretty large so I'm assuming it has something to do with it. Flights has 2,037,031 rows; Deals has 659,804 rows; and Ratings has 141,879.
Is there a faster/better way to get to the number I need (number of routes that have flights, deals, and ratings)?
EDIT
Here is the relevant schema info:
create_table "routes", force: true do |t|
t.integer "from_id"
t.integer "to_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "routes", ["from_id"], name: "index_routes_on_from_id", using: :btree
add_index "routes", ["to_id"], name: "index_routes_on_to_id", using: :btree
create_table "ratings", force: true do |t|
# various columns...
t.datetime "created_at"
t.datetime "updated_at"
t.integer "route_id"
end
add_index "ratings", ["route_id"], name: "index_ratings_on_route_id", using: :btree
create_table "flights", force: true do |t|
t.integer "airline_id"
t.integer "route_id"
# various columns...
end
add_index "flights", ["airline_id"], name: "index_flights_on_airline_id", using: :btree
add_index "flights", ["route_id"], name: "index_flights_on_route_id", using: :btree
create_table "deals", force: true do |t|
t.integer "route_id"
# various columns...
end
add_index "deals", ["route_id"], name: "index_deals_on_route_id", using: :btree
EDIT
I added boolean attributes to the Routes table of flightless and ratingless to help keep easier track after an import of whether the Route has any flights or ratings. After doing that, I've tried a few different queries and get different counts:
[7] pry(main)> Route.where(flightless: false, ratingless: false).includes(:deals).count
=> 19415
[8] pry(main)> Route.where(flightless: false, ratingless: false).joins(:deals).distinct.count
=> 10243
[9] pry(main)> Route.where(flightless: false, ratingless: false).joins(:deals).count
=> 378737
The query on line 8 produces the same result as Nic's pure SQL suggestion in the first answer below. I think I understand why the query on line 9 returns way more than could possible be correct (each route is duplicated for every resource it gets joined to) but I don't understand the difference between joins and includes well enough to tell why joins...distinct produces a different answer than includes here.
Using joins as well as includes will generate a cartesian product which will require some memory and depending on available resources can make the server unresponsive. Since you're after count and not the actual records, it should be enough to check for their existence, albeit probably at the expense of more calculations:
Route.where('EXISTS (SELECT 1 FROM ratings WHERE routes.id = ratings.route_id)
AND EXISTS (SELECT 1 FROM deals WHERE routes.id = deals.route_id)
AND EXISTS (SELECT 1 FROM flights WHERE routes.id = flights.route_id)').count
Although this mostly is raw SQL, it will return a proper ActiveRecord::Relation when count is skipped. This allows using it for on-demand loading of models like in find_in_batches.
This most probably can be rewritten using Arel to be more Rails way, but at this point I'm curious whether it actually gets you the proper result.
EDIT:
Or you could use
Route.joins(:ratings).joins(:deals).joins(:flights).distinct.count
It will return the correct count but will produce an intermediary cartesian product which it will then reduce with distinct. I would be curious to know what is the performance difference between these two approaches.

Ruby On Rails circular association definition

I'm building a Ruby on Rails application where I have a Locomotive and a Trip model.
Each locomotive can have many trips, and each trip belongs to one locomotive. The Trip model has a "begin" and "end" attribute, which are timestamps and can be null. Trip "end" is set once a locomotive on arrival of its destination; until then its' value is null.
Troubles start whenever I want to set the "end" attribute on the last trip for a particular locomotive ( id=45, for example). To this I must search within the set of all trips for a match on "locomotive_id=45" and search for those with a null "end" attribute. Only then I may be able to set the ending time.
In order to improve performance, I'm thinking of adding circular association with both models. Add a column to my Locomotive table named "last_trip_id", which can be null, that points to the Trip table and lets me know which row on that table is the last trip this locomotive has done.
I think this idea is awesome! :D However, I haven't found any documentation or tips regarding circular association on the ruby association guides or in it's API... So I don't know if this sort of implementation is encouraged on the RoR framework or not.
Would somebody give me some tips about this issue? Is it encouraged to add this circular association in order to have a better performance within this context?
Right now, my Locomotive and Trip models looks like this:
app/models/locomotive.rb
has_many :trips, dependent: :restrict_with_error
app/models/trip.rb
validates :locomotive_id, presence: true
belongs_to :locomotive
I think my new models should look like this:
app/models/locomotive.rb
has_many :trips, dependent: :restrict_with_error
belongs_to trip
app/models/trip.rb
validates :locomotive_id, presence: true
belongs_to :locomotive
has_one :locomotive
Which methods will add rails to my new models? Would I have some troubles when I write, for example:
> L=Locomotive.first
> L.trips.locomotives.trips.....
Best regards,
Karl
EDIT 1
Here you have my locomotives and trips structure:
db/schema.rb
...
create_table "locomotives", force: true do |t|
t.string "code"
t.datetime "created_at"
t.datetime "updated_at"
end
...
create_table "trips", force: true do |t|
t.integer "locomotive_id"
t.datetime "begin"
t.datetime "end"
t.datetime "created_at"
t.datetime "updated_at"
t.string "city_origin"
t.string "city_destination"
end
I would structure the schema like so: remember that with your schema you are trying to model real world concepts as clearly as possible, so always start with real world intuitive concepts.
Station #list of stations
Locomotive #list of trains
#these are the **scheduled** journeys
Route
start_station_id
end_station_id
departure_time
arrival_time
#these are the **actual** journeys
Journey
route_id
locomotive_id
actual_departure_time
actual_arrival_time
So, Stations, Locomotives and Routes all exist in advance, and then a Journey record is created for every actual real life journey by a train. The journey route_id and locomotive_id are filled when the journey is scheduled, and actual_departure_time and actual_arrival_time are filled in when the train actually leaves and arrives.
I think the names of the classes could be a bit better, and Journey could be decomposed further so that you have a table of start & end stations, and another with instances of these at different times, but you get the idea hopefully.
Your idea is to have an extra field from locomotive to the latest trip, so performance is better. If you set the right index on the trips table, maybe you don't need it.
Nevertheless, if you want to do it, here is how. Rails has the standard to set the name of the association to be identical to the classname. But this is no law. If your model get a little more complex, this is often no longer the right thing. So you can override it.
You call the association "latest trip" (or current_trip ?) and tell rails that it is actually a Trip-object.
locomotive.rb
belongs_to :latest_trip,
class_name: "Trip",
foreign_key: "latest_trip_id"
http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference
And you need to add a field "latest_trip_id" to the locomotives database table. You only need to be careful when you create and the objects, that all fields are correctly set.
You can have even several associations to Trip, like "funniest_trip", "accident_trips",....
You could introduce a Schedule model that would hold these trips. This would allow you to sort by date range for locomotives arriving/departing.
Just take care if your modeling trips that may run 'late' or 'off' schedule.
class CreateTrainManagementSystem < ActiveRecord::Migration
def change
create_table :locomotives do |t|
t.float :speed
end
create_table :schedules do |t|
t.integer :locomotive_id
t.integer :route_id
t.datetime :departure
t.datetime :arrival
end
create_table :routes do |t|
t.integer :origin
t.integer :destination
t.float :distance
end
create_table :trips do |t|
t.integer :schedule_id
t.datetime :departure
t.datetime :arrival
end
create_table :stations do |t|
t.string :title
t.string :address
t.string :city
t.string :state
t.integer :zip
t.decimal :coord_x
t.decimal :coord_y
end
end
end
To own this solution you can't just copy and paste it. This still needs models, validations and the like. It also solves more than just the performance issue. Think about calculating ETAs for delayed trains using speed, trip log, and route distances.
#Karl Hi, just saw in your comments that imply you're either a student or recent graduate. This is great news, you have a great resource here to ask questions. If you decide this is your craft and want to create solutions like mine, read Joe Celko's book Thinking In Sets.

Getting paranoia deleted objects through polymorphic relation in Rails 3

I have an Audit class which is used to store action, by and on attributes.
class Audit < ActiveRecord::Base
attr_accessible :activity
belongs_to :by, :polymorphic => true
belongs_to :on, :polymorphic => true
attr_accessible :by, :on
end
The polymorphic association for :by and :on is used to store any kind of objects which should be audited. Mainly because polymorphic is broken down into type and id in the schema and thus should be able to store all my model objects.
create_table "audits", :force => true do |t|
t.string "activity"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "on_id"
t.string "on_type"
t.integer "by_id"
t.string "by_type"
end
The problem I'm having comes from that all of the objects being audited are also using the paranoia gem. The paranoia gem introduces a deleted_at column in each models table which is checked for every query on that model through its default_scope which is set to something like "where(deleted_at is null)". The paranoia gem also provides a method .with_deleted which allows a direct query by turning of the default_scope and as a result also return objects which have beend paranoid / soft deleted.
However if I have any items that are deleted and I try to get all my Audited items listed using.
Audit.all
I can't figure out how to tell Rails to run the lookup query for each polymorphic :by and :on object adding the .with_deleted call. My guess is that rails looks up the the object of a polymorphic relation by
eval(type).find(id)
Which in my case would give me the object with the default_scope of the paranoia gem applied.
I have tried to override self.find_by_sql in Audit but with no luck. I get stuck in some Arel methods which I need to read up on more before I can move on.
I see the following solutions but I can't figure out how to do them.
Overwrite the polymorphic lookup. How?
Get the raw SQL as string before it's evaluated and sub/gsub the Where deleted_at is null part.
Any and all suggestions on how to approach this would be greatly appreciated.
Try this:
def on!
if on_type && on_id
on_type.constantize.with_deleted.find(on_id)
end
end
This will raise an ActiveRecord::RecordNotFound error if the record has been truly deleted, but otherwise will return the "on" object even if it is marked as deleted.

What's the difference between using t.integer and t.reference to define foreign keys in a rails migration

Given two classes, Apple and Orange:
def class Apple < ActiveRecord::Base
has_and_belongs_to_many :oranges
end
def class Orange < ActiveRecord::Base
has_and_belongs_to_many :apples
end
What's the difference between using t.integer to define the foreign keys in the join table:
create_table :apples_oranges, :id => false do |t|
t.integer :apple_id
t.integer :orange_id
end
and using t.references to define the foreign keys in the join table:
create_table :apples_oranges, :id => false do |t|
t.references :apple
t.references :orange
end
I've seen both and they appear to be interchangeable. Just wanted to make sure there isn't some subtlety/magic that I'm missing.
Oh, and I'm on Rails 3.2 w/MySQL
http://guides.rubyonrails.org/migrations.html#special-helpers
No magic, per se. Makes the migration more readable, more railsy, if you will, and if you are using polymorphism, adds the type column as well. So, either, but references is better, just because it is.