Rails ActiveRecord Model Linked List - ruby-on-rails

after intensive googling I will now state out a problem which seems to not occur often, but still is very basic. Linked Lists in Active Record. As far as I am now, we need two associations in the model:
class Child < ActiveRecord::Base
belongs_to :parent
belongs_to :next, :class_name => 'Child', :foreign_key => 'next_id'
belongs_to :previous, :class_name => 'Child', :foreign_key => 'previous_id'
end
So now we can get all children of a parent:
children = Child.where("parent_id = ?", parent_id)
And now to the question: I want of course to get all children from the database with one query, but I also want to go through the children in the linked order, which means first one will be the child with the previous attribute of nil, the next child will be the one which is connected by the firsts next attribute, and so on until the next attribute is nil.
Is it possible to do it like this, or do I need to query the first child, and then go from child to child without "precaching"?

The resort and ranked-model gems are other alternatives.
The first one uses an approach similar to linked lists. The second one uses a position attribute.

You should probably use the Rails acts_as_list gem. It stores the position of the item in the list, and even scopes to parent objects for lists and belongs_to. it would solve this problem as well by allowing you to query all the elements and then sort them correctly.

Related

How can I stop collection<<(object, …) from adding duplicates?

Given a Dinner model that has many Vegetable models, I would prefer that
dinner.vegetables << carrot
not add the carrot if
dinner.vegetables.exists? carrot
Yet it does. It will add a duplicate record every time << is called.
There is a :uniq option you can set on the association, but it only FETCHES AND RETURNS one result if there are multiples, it doesn't ENFORCE unique values.
I could check for exists? every time I add an obj to a collection, but that is tedious and error-prone.
How can I use << freely and not worry about errors and not check for already existing collection members every time?
The best way is to use Set instead of Array:
set = Set.new
set << "a"
set << "a"
set.count -> returns 1
You can add an ActiveRecord unique constraint if you have a join model representing a many-to-many relationship between dinners and vegetables. That's one reason I use join models and has_many :through as opposed to has_and_belongs_to_many. It's important to add a uniqueness constraint at the database level if possible.
UPDATE:
To use a join model to enforce constraint you would need an additional table in your database.
class Dinner
has_many :dinner_vegetables
has_many :vegetables, :through => :dinner_vegetables
end
class Vegetable
has_many :dinner_vegetables
has_many :dinners, :through => :dinner_vegetables
end
class DinnerVegetable
belongs_to :dinner
belongs_to :vegetable
validates :dinner_id, :uniqueness => {:scope => :vegetable_id} # You should also set up a matching DB constraint
end
The other posters' ideas are fine, but as another option you can also enforce this on the database level using e.g. the UNIQUE constraint in MySQL.
After a lot of digging, I've discovered something cool: before_add, which is an association callback, which I never knew even existed. So I could do something like this:
has_many :vegetables, :before_add => :enforce_unique
def enforce_unique(assoc)
if exists? assoc
...
end
Doing this at the DB level is a great idea if you REALLY NEED this to be unique, but in the case that it's not mission critical the solution above is enough for me.
It's mostly to avoid the icky feeling of having extra records lying around in the db...

Rails, self-joined table, proper way of creation

For my web application I need to implement a supervisor/student relationship. I need to join my "Person" table with itself through the "Supervision" table.
class Person < ActiveRecord::Base
has_many :supervised, :class_name => 'Supervision', :foreign_key => 'supervisor_id'
has_many :supervisors, :class_name => 'Supervision', :foreign_key => 'supervised_id'
end
class Supervision < ActiveRecord::Base
belongs_to :supervised, :class_name => 'Person'
belongs_to :supervisor, :class_name => 'Person'
end
Now I need help regarding the controller. I'm not sure if I need two controllers, one for supervised and one for supervisors, or just one "Supervision" controller.
Both the student and supervisor must be able to create a "Supervision". I'm just not sure how to let the controller know whether the current user needs to be the supervisor or the student. Any thoughts?
You could create two controllers, but that would not be DRY, so it is probably best avoided. You can either set up your routes so the URLs for Prof/Student appear to be different, but actually map to the same controller.
How many students a prof has:
the_prof = Person.find( *my record number* )
the_prof.supervised.count
Who they are is that same thing, so show their names
the_prof.supervised.each do |student|
puts student.name
end
How to determine who is a professor or not? I would add a boolean flag to the people table: is_prof
My initial thought was the way to determine if a person was a student is they have no supervised. If a professor, they have no supervisor, but that breaks down if a Professor gets rid of all his students or the Student gets rid of all his Professors. Suddenly, we're in the land of undefined, which is BAD.
The flag also makes it easy to segregate all the professors and student, so you can do
profs = Person.find_by_is_prof( true )
studs = Person.find_by_is_prof( false )
(make sure to index that field in your database)
I'm guessing you'll end up making at least two controllers: one for people acting as supervisors, ie: one to manage one's subordinates; and one to manage one's own person record. You may even have another which manages a person's sign up and allows them to select their supervisor, as part of a kind of a wizard-style step.
Try mocking out how you want the application to behave first by sketching out some wireframes. Those will help you figure out which resources need to be changed from where.
If you find, for instance, that you need a list of one's subordinates, then that's probably a SubordinatesController#index page. Adding a subordinate would probably be a #new / #create pair in that controller.
Controllers are really about figuring out how the UI will respond to different user actions. Setting /my/ supervisor, and noting that I'm /someone else/'s supervisor are probably to very different things at the UI level. Just because they happen to reside in the same table doesn't mean that the UI has to reflect that symmetry.
It's strange that one would be able to change their own supervisor list. I think that's where the weirdness of your question arises.
Perhaps that's actually a side-effect of changing some other membership, like moving to a different group in the organization, in which case the reassignment would be part of its own controller.

Rails Advanced Sorting

I have three models, basically:
class Vendor
has_many :items
end
class Item
has_many :sale_items
belongs_to :vendor
end
class SaleItem
belongs_to :item
end
Essentially, each sale_item points to a specific item (but has an associated quantity and sale price which might be different from the item's base price, hence the separate model), and each item is made by a specific vendor.
I'd like to sort all sale_items by vendor name, but this means going through the associated item, because that's where the association is.
My first attempt was to change SaleItem to the following:
class SaleItem
belongs_to :item
has_one :vendor, :through => :item
end
Which allows me to look for SaleItem.first.vendor, but doesn't allow me to do something like:
SaleItem.joins(:vendor).all(:order => "vendors.name")
Is there an easy way to figure out these complex associations and sorting? It would be especially great if there were a plugin that could take care of these sort of things. I have a lot of different types of tables to add sorting to in this application, and I feel like this will be a big chunk of the figuring-out work.
This could definitely be done with a more complex SQL query (possibly using find_by_sql), but you could also do it pretty easily in Ruby. Try something like the following:
SaleItem.find(:all, :include => { :items => :vendors }).sort do |first,second|
first.vendor.name <=> second.vendor.name
end
I haven't tested it, so it might not work exactly like this, but it should give you a good idea of one possible solution.
Edit: Found an old blog post that seems to have solved this issue. Hopefully this still works in the lastest version of ActiveRecord.
source: http://matthewman.net/2007/01/04/eager-loading-objects-in-a-rails-has_many-through-association/
Second Edit: Straight from the Rails documentation
To include a deep hierarchy of associations, use a hash:
for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
That’ll grab not only all the comments but all their authors and gravatar pictures. You can mix and match symbols, arrays and hashes in any combination to describe the associations you want to load.
There's your explanation.
Do you really need your sale_items sorted by the database, or could you wait until it is presented and do the sorting client side via javascript (there are some great sorting libraries out there) - that would save server CPU and (backend) code complexity.

eager loading association on a subclass

I have the following (simplified) class hierarchy:
def Parent < ActiveRecord::Base end
def Child < Parent
belongs_to :other
end
def Other < ActiveRecord::Base end
I want to get all Parent objects and -if they are Child objects- have them eager load the :other association. So I had hoped I could do:
Parent.find(:all, :include => [:other])
But as I feared, I get the message: "Association named 'other' was not found; perhaps you misspelled it?"
What is the best way to establish eager loading in this scenario?
[Edit]
As requested, here's the more concrete example:
Parent = Event
Child = PostEvent
Other = Post
I want to log different types of events, all with their own properties (some of which are references to other objects), like the above example. At the same time I want to be able to list all Events that occurred, hence the parent class.
Can you define the belongs_to :other association in the Parent model? It won't be relevant to every Parent object, but that's the nature of STI: you will almost always have some column that's not used by every child.
If you really can't move the association to the parent, you may have to load in two steps, for example:
Parent.find(:all, :conditions => "type != 'Child'") +
Child.find(:all, :include => [:other])
Since Child inherits from Parent (and not the other way around), Parent has no knowledge of the belongs_to :other association.
I think you need to reconsider how you're modeling your app. Perhaps some specifics on your actual models would raise some answers on alternative methods of what you're trying to accomplish.

Rails has_many_polymorphs in reverse?

So I have some models set up that can each have a comment. I have it set up using has_many_polymorphs, but I'm starting to run into some issues where it's not working how I think it should.
For example:
class Project < ActiveRecord::Base
end
class Message < ActiveRecord::Base
has_many_polymorphs :consumers,
:from => [:projects, :messages],
:through => :message_consumers,
:as => :comment # Self-referential associations have to rename the non-polymorphic key
end
class MessageConsumer < ActiveRecord::Base
# Self-referential associations have to rename the non-polymorphic key
belongs_to :comment, :foreign_key => 'comment_id', :class_name => 'Message'
belongs_to :consumer, :polymorphic => true
end
In this case, the Message wouldn't get deleted when the Project is removed, because the Message is really the parent in the relationship.
I simplified it a little for the example, but there are other models that have have a Message, and there are also Attachments that work similarly.
What would be the correct way to set this up so that the children get removed when the parent is deleted? I'm hoping to not have a million tables, but I can't quite figure out another way to do this.
When you say "so that the children get removed when the parent is deleted?", can you give an example? I.e. when a project is deleted I want all its messages to be deleted too? What happens when you delete a message, do you want anything else (e.g. all corresponding message_consumer entries) to be deleted as well?
UPDATE
OK, so has_many_polymorphs will automatically delete "orphaned" message_consumers. Your problem is that a message may have more than one consumer, so deleting a project may not be sufficient grounds for deleting all its associated messages (as other consumers may depend on those messages.)
In this particular case you can set up an after_destroy callback in MessageConsumer, to check whether there still exist other MessageConsumer mappings (other than self) that reference the Message and, if none exist, also delete the message, e.g.:
class MessageConsumer < ActiveRecord::Base
...
after_destroy :delete_orphaned_messages
def delete_orphaned_messages
if MessageConsumer.find(:first, :conditions => [ 'comment_id = ?', self.comment_id] ).empty?
self.comment.delete
end
end
end
All this is happening inside a transaction, so either all deletes succeed or none succeed.
You should only be aware of potential race conditions whereby one session would arrive at the conclusion that a Message is no longer used, whereas another one may be in the process of creating a new MessageConsumer for that exact same Message. This can be enforced by referential integrity at the DB level (add foreign key constraints, which will make on of the two sessions fail, and will keep your database in a consistent state), or locking (ugh!)
You could simplify this a lot by using acts_as_commentable.

Resources