How can I limit has_many to certain scopes in Rails 4? - ruby-on-rails

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

Related

Rails abstract class has_many

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.

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

DRY way to add same condition on many queries

I use rails 3.0.5
I've User and Company models, a user belongs_to a Company.
The Product, Bill, a several others also belongs_to a Company.
For obvious reasons, a User can acts on a Product, Bill... only if the Product's Company is the same as the User's Company.
I can use custom scope queries adding the condition on the company for everything, but it's not DRY at all.
How would you do that the the nice way?
Thanks
#product = current_user.company.products.find params[:id]
should work. If you want to DRY it up further I'd recommend using the plugin InheritedResources which has a sweet method called begin_of_association_chain (see in the README under overwriting defaults) that let's you define this stuff globally.
Have you looked into named scopes? http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html
Seems like exactly what you are looking for
You can define a association
has_many :products, :through => :company
Then you can do
user.products.find(params[:id])
Not sure if this is the right way to do it!!!

How do you scope ActiveRecord associations in Rails 3?

I have a Rails 3 project. With Rails 3 came Arel and the ability to reuse one scope to build another. I am wondering if there is a way to use scopes when defining a relationship (e.g. a "has_many").
I have records which have permission columns. I would like to build a default_scope that takes my permission columns into consideration so that records (even those accessed through a relationship) are filtered.
Presently, in Rails 3, default_scope (including patches I've found) don't provide a workable means of passing a proc (which I need for late variable binding). Is it possible to define a has_many into which a named scope can be passed?
The idea of reusing a named scope would look like:
Orders.scope :my_orders, lambda{where(:user_id => User.current_user.id)}
has_many :orders, :scope => Orders.my_orders
Or implicitly coding that named scope in the relationship would look like:
has_many :orders, :scope => lambda{where(:user_id => User.current_user.id)}
I'm simply trying to apply default_scope with late binding. I would prefer to use an Arel approach (if there is one), but would use any workable option.
Since I am referring to the current user, I cannot rely on conditions that aren't evaluated at the last possible moment, such as:
has_many :orders, :conditions => ["user_id = ?", User.current_user.id]
I suggest you take a look at "Named scopes are dead"
The author explains there how powerful Arel is :)
I hope it'll help.
EDIT #1 March 2014
As some comments state, the difference is now a matter of personal taste.
However, I still personally recommend to avoid exposing Arel's scope to an upper layer (being a controller or anything else that access the models directly), and doing so would require:
Create a scope, and expose it thru a method in your model. That method would be the one you expose to the controller;
If you never expose your models to your controllers (so you have some kind of service layer on top of them), then you're fine. The anti-corruption layer is your service and it can access your model's scope without worrying too much about how scopes are implemented.
How about association extensions?
class Item < ActiveRecord::Base
has_many :orders do
def for_user(user_id)
where(user_id: user_id)
end
end
end
Item.first.orders.for_user(current_user)
UPDATE: I'd like to point out the advantage to association extensions as opposed to class methods or scopes is that you have access to the internals of the association proxy:
proxy_association.owner returns the object that the association is a part of.
proxy_association.reflection returns the reflection object that describes the association.
proxy_association.target returns the associated object for belongs_to or has_one, or the collection of associated objects for has_many or has_and_belongs_to_many.
More details here: http://guides.rubyonrails.org/association_basics.html#association-extensions
Instead of scopes I've just been defining class-methods, which has been working great
def self.age0 do
where("blah")
end
I use something like:
class Invoice < ActiveRecord::Base
scope :aged_0, lambda{ where("created_at IS NULL OR created_at < ?", Date.today + 30.days).joins(:owner) }
end
You can use merge method in order to merge scopes from different models.
For more details search for merge in this railscast
If you're just trying to get the user's orders, why don't you just use the relationship?
Presuming that the current user is accessible from the current_user method in your controller:
#my_orders = current_user.orders
This ensures only a user's specific orders will be shown. You can also do arbitrarily nested joins to get deeper resources by using joins
current_user.orders.joins(:level1 => { :level2 => :level3 }).where('level3s.id' => X)

How to access and submit related polymorphic models in the same form, in Rails?

Suppose I have 3 models, Car, Motorcycle and Truck, and for each I have to enter a bunch of stuff, such as a list of known previous owners, traffic tickets, license plates, etc. So I created a model for each (PreviousOwners, PreviousPlates, etc) and set up polymorphic associations for the related models.
The problem is, how can I enter all of that using just one form, kind of like this:
Car #123
Known previous owners:
Jason Jazz
Brian Bass [add another]
Known previous license plates:
12345
67890 [add another]
Current status:
Cleared
(this is a dropdown select menu, CurrentStatus is also a polymorphic association, but with predefined values.)
etc
This is proving to be a bitch, way beyond my level of expertise (newbie here). The resources are not nested and almost everything I find on multiple models is for nested resources, and nothing seems to apply to polymorphic associations.
(This is just an example, I know ideally I should have a Vehicle model with 'Car', etc, as categories, but it's just to illustrate the real need for polymorphic models in my case.)
Thanks.
Maybe the PresenterPattern is helpfull too:
http://blog.jayfields.com/2007/03/rails-presenter-pattern.html
The basic idea is to create a presenter which acts like a model and processes all the incoming data from your form and distributes it to the models. This way it's also easy to create multiple instances of lets say PreviousOwner and attach it to Car.
Check the link out!
You can use the new nested attributes in Rails 2.3, but there is a certain way you have to write it to make it work. The trick is that you need to create the actual polymorphic object, then build the class that has the belongs to polymorphic clause in it. This is an example I found at Ryans Scraps, posted by a user named: Superslau (I've cleaned it up a good bit for here):
This feature is really awesome. I have
implemented this with polymorphic
associations, and it works!
class Task < ActiveRecord::Base
has_many :assets, :dependent=>:destroy
accepts_nested_attributes_for :assets, :allow_destroy => true
belongs_to :workable, :polymorphic => true
end
class Upload < ActiveRecord::Base
has_one :task, :as => :workable, :dependent=>:destroy
accepts_nested_attributes_for :task, :allow_destroy => true
end
Upload is a kind of task. All tasks
can have one or more assets uploaded.
I took me a while to figure out that I
should use the Upload model as the
parent. So in one form, I can create
an upload, and it’s corresponding task
entry, along with a file upload.
in my controller:
def new
#upload = Upload.new
#upload.task = Task.new
#upload.task.assets.build
end
Don’t
worry if that doesn’t make any sense,
I just wanted to let people know that
accepts_nested_attributes_for works
just fine with polymorphic
associations. Thanks Eloy!
Very well, nested form builders doesn't have to be associated with nested resources AFAIK.Can you post your models code as well?
There is a RailsCast on Complex Forms that might help you with building a single form from multiple models.
If the car/motorcycle/truck models are identical, you should add a type column to your vehicle model. If they're not, you should use STI (single table inheritance).
But yeah, need to see your models first before I can give you code.
You can avoid this and make things a bit simpler by introducing a Vehicle model. The Vehicle model can have all your PreviousOwners, PreviousPlates, etc collections, and then your Truck, Car and Motorcycle models can has_one Vehicle.

Resources