rails named_scope and :source - ruby-on-rails

I am a beginner in Rails,
Can we use :source with named scope?
I am able to use it with has_many and other associations
Thanks
Mark

No, you can't because you don't need.
A named scope is part of the model where defined in.
class Post
named_scope :published, :conditions => { :published => true }
end
However, this doesn't prevent you from using a named scope through an association.
class Category
has_many :posts
end
category.posts # => all posts
category.posts.published # only published posts

If you can use it in a find() call, generally you can use it with a named scope. The parameters for find are itemized in the documentation (http://apidock.com/rails/ActiveRecord/Base/find/class) but I'm not sure that source is one of them. As far as I know, that's for a has_many relationship sort of thing, not for find.
However, named scopes can be applied to relationships, so perhaps that's what you're intending.

Related

Rails association definition has "magic number"

I am an beginner/intermediate Rails developer and I have inherited a large Rails project. While digging through the codebase I found that one of our models has association definitions that rely on a hard-coded ID:
class Account < ActiveRecord::Base
has_many :customer_rels, :class_name => 'EntityAccountRel', :conditions => 'entity_account_rels.entity_type_id=1'
has_many :vendor_rels, :class_name => 'EntityAccountRel', :conditions => 'entity_account_rels.entity_type_id=2'
has_many :customers, :through => :customer_rels, :source => :entity
has_many :vendors, :through => :vendor_rels, :source => :entity
customer and vendor are both Entities, with a relationship to Account (through EntityAccountRel). They are distinguished from one another by entity_type_id, which refers to EntityType, a model whose few members are static enough to let us get away with referring to them by hard-coded ids. I recognize this is a bad practice, but I'm wondering about the 'best' way to refactor:
1) redefine the association definition to use a subquery like so:
has_many :customer_rels, :class_name => 'EntityAccountRel', :conditions => 'entity_account_rels.entity_type_id=(select id from entity_type where name="Customer")'
This is the most obvious solution to me, but also seems terribly inelegant and likely inefficient as well
2) define a scope on EntityAccountRel like so:
scope :customer, joins(:entity_type).where('entity_type.code="CUST"')
and then somehow tie this to the association definition:
has_many :customer_rels, :class_name => 'EntityAccountRel', :scope => :customer
this doesn't work, (Unknown key(s): scope (ArgumentError)) but seems like maybe there would be support for it if I knew how to define the association correctly
3) set up an inheritance relationship on EntityAccountRel, obviating the need for entity_type_id (and maybe EntityType as well):
class CustomerAccountRel < EntityAccountRel
This would simplify my association definition but it seems like a major refactor with far-reaching implications. Plus I'm told to use inheritance cautiously, as it can make code difficult to understand.
These are all the options I've come up with, but I think I'm missing something obvious.
EntityType#id seems to be considered static and strongly related to code behavior, so you could give them names as constants:
class EntityType
CUSTOMER = 1
VENDOR = 2
end
and then just substitute the magic number by the constant. That is quick and gives you a good return as you can reference that in other parts of the code easily. I'd keep all references to these constants in the model layer.

Iterating Over Rails Model Classes with Polymorphic Associations

In an application there are many models with polymorphic associations defined like:
has_many :mentions, :as => :mentionable, :dependent => :destroy
In a library class all mentionable models are collected for later iterating.
mentionables = Model1.all + Model2.all + Model3.all ...
This works but it is just static which is not desirable for a library code. Bottom statement would be much more intuitive hovever it won't work.
mentionables = Mentionable.all
Is there an API in Rails to iterate over models with Polymorphic relations defined with ':as => ...' directive?
I think there are two questions in here and I'll try my best to answer both of them. First, you'd need a way to list all of the models in your Rails application. There are methods offered in this question, and I prefer this answer (by sj26).
# Really depends on how your classes are loaded, usually environment specific.
Rails.application.eager_load!
ActiveRecord::Base.descendants
After, you need to parse the model associations to determine if they have an :as #option assigned, and pull out the associated class name. This is assuming that your association has been created as such:
class Mentionee < ActiveRecord::Base
has_many :mentions, :as => :mentionable, :dependent => :destroy, :class_name => 'Mentionable'
end
You can do this using the reflect_on_all_associations method (there is probably a more ruby-esque way to write this):
Mentionee.reflect_on_all_associations.select {|a| a.options[:as] == :mentionable }
Which will return the polymorphic class model for Mentionable. To join these up, you could do something as follows (untested!):
Rails.application.eager_load!
mentionables = []
ActiveRecord::Base.descendants.each do |descendent|
mentionables << descendent.reflect_on_all_associations.select{|a| a.options[:as] == :mentionable}
end
mentionables.each do |mentionable|
# Do your work here
end

Rails - combine multiple has_many throughs

I'm just started with Rails, and have a problem I can't solve myself:
User.rb:
has_many :bid_listings, through: :bids, source: :listing, uniq: true
has_many :offer_listings, through: :offers, source: :listing, uniq: true
Both of these return listings, and using methods/scopes from Listing model individually works perfectly. However, when I'm trying to combine these, I'm getting an Array, where i can't apply Listing model's methods and scopes.
I've tried multiple ways, but stuck. Please help.
P.S. User has many bids, User has many offers, bid belongs to listing, offer belongs to listing
You're calling an instance method on an Array object, as opposed to on an ActiveRecord object. Therefore, object of type Array has no idea what the search method is. Try this out:
Edit
user = User.first
listings = Listing.joins(:bids).joins(:offers).where(:bids => {:user_id => user.id}, :offers => {:user_id => user.id})
listings.search('a')
I had a similar problem, and the best solution I came up with was something like:
def buying_listings
Listing.find_by_sql(bid_listings.union(offer_listings).to_sql)
end
This way should still allow you continue scoping, but is less efficient as it will execute an extra query.

Has many through associations with conditions

I am trying to add a condition to a has many through association without luck. This is the association in my video model:
has_many :voted_users, :through => :video_votes, :source => :user
I want to only get the voted_users whose video_votes have a value equal to 1 for that video. How would I do this?
I would suggest creating a model method within the video model class
Something like:
def users_with_one_vote
self.voted_users, :conditions => ['value = ?', 1]
end
Then in the controller use video.users_with_one_vote
Then testing is easier too.
Any chance you can change that column name from 'value'. Might give some issues (reserved?).
I'd do this in 2 stages:
First, I'd define the has_many :through relationship between the models without any conditions.
Second, I'd add a 'scope' that defines a where condition.
Specifically, I'd do something like:
class User < ActiveRecord::Base
has_many :video_votes
has_many :votes, :through=>:video_votes
def self.voted_users
self.video_votes.voted
end
end
class VideoVote
def self.voted
where("value = ?", 1)
end
end
class Video
has_many :video_votes
has_many :users, :through=>:video_votes
end
Then you could get the users that have voted using:
VideoVote.voted.collect(&:user).uniq
which I believe would return an array of all users who had voted. This isn't the exact code you'd use -- they're just snippets -- but the idea is the same.
Would
has_many :voted_users, :through => :video_votes, :source => :user, :conditions => ['users.votes = ?', 1]
Do the trick?
I found that defining this method in my model works:
def upvoted_users
self.voted_users.where("value = 1")
end
and then calling #video.upvoted_users does the trick.
The best way to do this without messing with the relations is by crafting a more complex query. Relations is not the best thing to use for this particular problem. Please understand that relations is more a "way of data definition" then a way of "bussiness rules definition".
Bussiness logic or bussiness rules must be defined on a more specifically layer.
My suggestion for your problem is to create a method to search for users who voted on your video only once. something like:
class Video < ActiveRecord::Base
def voted_once()
User.joins(:video_votes).where("video_votes.value == 1 AND video_votes.video_id == ?", this.id)
end
Rails is magical for many things, but complex queries still have to be done in a "SQL" way of thinking. Don't let the illusional object oriented metaphor blind you
As long as we are throwing around ideas, how about using association extensions.
class VideoVote
scope :upvotes, where(:value => 1)
end
class Video
has_many :voted_users, :through => :video_votes, :source => :user do
def upvoted
scoped & VideoVote.upvotes
end
end
end
Then you feel good about making a call with absolutely no arguments AND you technically didn't add another method to your Video model (it's on the association, right?)
#video.voted_users.upvoted

ActiveScaffold: How to create a drop-down select for polymorphic association?

I'm trying to create a drop-down select box for a polymorphic association with ActiveScaffold.
I have:
class Award
belongs_to :sponsorship, :polymorphic => :true
end
class Organization
has_many :awards, :as => :sponsorship
end
class Individual
has_many :awards, :as => :sponsorship
end
While trying to create a select drop-down box in awards_controller
with:
config.columns[:sponsorship].form_ui = :select
I get the following error:
ActionView::TemplateError
(uninitialized constant
Award::Sponsorship)
I'm not sure if it's something I'm not doing right or what I'm trying
to accomplish not directly supported in AS.
Would really appreciate some advice.
I'm not familiar with ActiveScaffold... But, a quick pass in their documentation revealed a section about has_many :through which I am familiar with from ActiveRecords... so for what it's worth, is it possible that your polymorphic associations should be written like this?:
class Organization
has_many :awards, :through => :sponsorship
end
class Individual
has_many :awards, :through => :sponsorship
end
I'm not sure of what you're trying to do, but rails is indeed true when saying that there is no ":sponsorship'.
When polymorphism is used, rails automatically create two columns, in your case : *sponsorship_id* and *sponsorship_type*.
You may want to use one of those.
However, I'm not familiar with ActiveScaffold form_ui, so I cannot help you further.
I am getting this error, but only if I have an instance of Award with no sponsorship (my names are different...). So presumably the OP and follow up posters got past this, but for future readers, make sure you don't create an instance of the dependent model when using a polymorphic association with active_scaffold...

Resources