Rails abstract class has_many - ruby-on-rails

As the title suggests, this isn't going to make any sense. Imagine the scenario:
I have the following models: Game, GameType, and Champion. I would like only games of a certain GameType (like MOBA) to have a has_many relationship to the Champion model; where others (like FPS, etc.) would not.
My first inclination was to make a GameTypeMoba abstract class, where all classes that inherit from it could have some of its properties (such as having champions). However, I know this doesn't make sense since a class that is not tied to a table can't have table relationships. Further, it just seems like a shitty, WET (opposite of DRY) approach if I could somehow hack it together.
I hope someone has a simple solution that doesn't involve messy app logic. Although I'd also accept "retard, go to bed" at this point as well.

Checking out the Rails Guides "has_many" association reference (http://guides.rubyonrails.org/association_basics.html#has_many-association-reference), you may be able to use the condition option on the association declaration. The example provided in the documentation:
class Customer < ActiveRecord::Base
has_many :confirmed_orders, :class_name => "Order",
:conditions => "confirmed = 1"
end
In your situation, I assume you would want to use the :class_name of "GameType" with :conditions => "MOBA = ".

Mischa is right, in this case it doesn't seem like there's anything better/cleaner that can be done. And having an unneeded relation for a subset of records isn't really a big deal.

Related

How to conditionally include association definitions?

I'm able to get the behavior I'd like if I create a Foo class, some STI classes that inherit it (Foo1, Foo2, etc.) and then to add different has_many :bars associations to each class. For example:
class Foo < ApplicationRecord
has_many :bars
end
class Foo1 < Foo
has_many :bars, -> { where(user_id: 1) }
end
I want to be able to call bars on an object and get a different association behavior depending on the state of the object. This works, but is there a way to do it without setting up STI?
I tried doing everything inside foo.rb but I seem to be loading my first has_many :bars definition even if I do something like this:
has_many :bars ... if some_method_returning_boolean?
has_many :bars ... if some_other_method_returning_boolean?
And even if this did work, it seems kind of clunky.
I also considered scopes, but as far as I understand scopes then I'd have to call foo.bars.something and foo.bars.something_else instead of relying on the state of the object foo to give me foo.bars differently. Also, it looks like scopes only cover part of a has_many definition but can't modify arguments like :source, :foreign_key, but I may be wrong.
Is there another way?
STI is the correct way of doing this. It's generally understood that any particular model class should have well-defined and consistent relationships with other records.
If you have relationships that only apply to certain kinds of records that's exactly what STI is for: Create a subclass that defines these relationships.
You see this all the time. An example would be that both "Parent" and "Student" are of base type "Person", but that the students have a belongs_to: parent association and the parents have a has_many: children relationship.
Of course this presumes parents are unlikely to become students, and students parents, an assumption that may not be correct. Whatever assumptions you make, I hope there's some serious thinking about if these restrictions are relevant or just overly paranoid.
It's generally bad form to switch types on a record even though it can technically be done. You could use this to adjust how a record's relationships are defined if you think this is strictly necessary.
Generally I'd advise you to stick with a consistent relationship structure. Those records which should not be related to anything don't need those methods physically removed, they can be there while not doing anything useful. They come along for free anyway.
Maybe you will be satisfacted by overwrite your bars method in this way ?
This super just return usual result from something_foo.bars.
class Foo
def bars
super.where(something: something_value) if your_condition
super.where(something_else: something_value) if other_condition
end
end

How can I limit has_many to certain scopes in Rails 4?

I am setting up a situation where I only want certain scopes of my model to "has_many" of another model. As a hypothetical situation, imagine I have a model called Tree and a model called Apple. I only want tall trees to be allowed to have apples, so I scoped my tree model into tall. How can I use a has_many appropriately in this situation?
I am using Rails 4.
Sample Code
Tree.rb
scope :tall, where(:tall => true)
scope :short, where(:short => true)
has_many :apples (need to alter this line to only have this happen for :scope tall)
Apple.rb
belongs_to :tall, class_name: 'Tree'
I am unsure if the rest of the code is the right way either, so please let me know if there are any improvements I can make to it.
The gem 'pundit' makes it easy to create and allow model specific and oo procedures. May be what you are looking for.
https://github.com/elabs/pundit

Rails 3 - A model with a one to one relationship to itself - do I need belongs_to

I have a model named Person. It has two properties - name and parent_person_id
A person will always have a parent person.
Should I be using belongs_to in the model? If so, what are the advantages of doing so.
class Person < ActiveRecord::Base
belongs_to :person
end
I've not tried this code out yet, it seems a bit wrong my normal mysql ways.
I'm looking for opinions here more than anything, I'm quite new to the rails and want to make sure I'm doing things properly, doing things 'the Rails way'.
I'd suggest using a gem like ancestry for a tree structure like that. It gives you your association plus lots of utility methods (finding parent, children, siblings, retrieving a subtree).
If you don't want that, then in your belongs_to association has to look like this:
belongs_to :person, :foreign_key => "parent_person_id"
since without that option, rails would look for a foreign key of person_id and, not finding that, light your CPU on fire throw an error message.
Yes, you would need that belongs_to since this is what will tell rails about this relationship.

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.

Can I override what an association method returns?

In Rails, I want to override the behavior of an association. For example, by default, if Person has_many :hats, calling some_person.hats would do a simple join using person.id and hat.person_id.
I want to modify that query to include some other criteria. For example, maybe a person's collection of hats should be just the hats that are appropriate to their country.
It seems that I could do something like this:
class Person < ActiveRecord::Base
has_many :hats, :through => :country do
# John lives in Canada, so he gets a baseball cap and a hockey helmet
self.country.hats
end
end
Can I control what an association returns like this? If not, would a scope be the best solution?
I know this is a silly example, but explaining the domain logic that I need this for would be way too boring for everyone here. :)
Scopes are probably your best option because they're chainable and reusable outside your association. Otherwise, you could use association extensions. Check out this thread for more info. Association Extensions

Resources