Getting paranoia deleted objects through polymorphic relation in Rails 3 - ruby-on-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.

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

Beginner Rails Migration & Column Options in ActiveRecord

Quick question on :null option in ActiveRecord table creation.
Let's say I was to create a new table with a column description.
def change
create_table :products do |t|
t.string :name
t.text :description, null: false
end
end
If I don't have any :presence validation in my model regarding the description column, then shouldn't "nothing" be able to be passed into the description column? I'm not sure what null: false can do to stop me from passing nothing in since I don't have any validation in place.
Rails migration or schema options like null: false operate on database level (storage). If you tried to save record with empty description field, you would receive a database error (e.g. generated by PostgreSQL) wrapped in a ActiveRecord::StatementInvalid:. On calling object.valid? the object would have been valid from the application point of view (Rails).
Rails validations like :presence operate on application level. In this case passing a null value would create an object which would return false on valid? and you could easily access error messages from the object by calling object.errors. If not bypassing validations, Rails would not allow you to save such a record to the database.

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.

join table creating unintentional NULL entries in rails

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.

referencing attributes in models with belongs_to relationships through a nested namespace

Ok, so I thought I understood how the relationship specifications work in rails but I've been struggling with this for a day now.
Some context, I have two models Cars & Model Names (e.g. Impala, Charger, etc), where Cars are instances of Model Names, and Model Names really is nothing more than a lookup table of Model Names and some other model level attributes. The Model Name controller is nested within the admin namespace as only admins can CRUD Model Names. End users can add instances of cars to the Cars model.
So, in routes.rb I have:
resources :cars
namespace :admin do resources :model_names end
The Model's are defined as:
class Admin::ModelName < ActiveRecord::Base
end
class Car < ActiveRecord::Base
belongs_to :admin_model_name
end
The Migrations are:
class CreateCars < ActiveRecord::Migration
def self.up
create_table :cars do |t|
t.string :chassis_number
t.string :description
t.references :admin_model_name
t.timestamps
end
end
class CreateAdminModelNames < ActiveRecord::Migration
def self.up
create_table :admin_model_names do |t|
t.string :model
t.integer :sort_index
#...additional attributes removed
t.timestamps
end
The admin CRUD of ModelName all work great. The problem is in the Car views. I think I should be referencing a particular cars model name like such:
<%= #car.admin_model_names.Model =>
But I get the error:
undefined method `admin_model_names' for #<Car:0x000001040e2478>
I've tried the attr_accessible on the ModelNames model but to no avail. The underlying data is referenced correctly. I have also have HABTMT relationship between Cars & Users and that all worked fine referencing each others attributes from the different entities views. But haven't been able to get this to work. Is it due to the nested resource & admin control inheritance?
The only referencing method I found that works is:
<%= Admin::ModelName.find(#car.admin_model_name_id).model %>
But this really seems to be too much code (and expense of a find) to get to that attribute. Is there a Rails way?
Thanks in advance.
Scott
Have you tried:
class Car < ActiveRecord::Base
belongs_to :admin_model_name, :class_name => "Admin::ModelName"
end
as stated in
http://guides.rubyonrails.org/association_basics.html
section 3.4?
you may also need to set the :foreign_key => "admin_model_name_id" attribute to specify the referencing model.
Hope it helps.
Did you try
class Car < ActiveRecord::Base
belongs_to :admin_model_name, :class_name => 'Admin::ModelName'
end
and if necessary add :foreign_key => '' and add this column to your cars table.