I want to fetch all the items being followed and the for each item I want to fetch all of it's articles in an array to pass it to the view partial.
But I am not receiving objects rather I am receiving Active Record relation
Here is my code
#row=[]
#followed_blogs = Follow.where("followable_type == ?","Blog").where(follower_id: current_user.id)
#followed_blogs.each do |blog|
#row << Article.where("blog_id == ?",blog.followable_id)
end
#articles = #row.sort! {|a,b| a.created_at <=> b.created_at}
#followed_blogs = Follow.
where(followable_type: "Blog").
where(follower_id: current_user.id).
includes(:articles)
#row = #followed_blogs.map(&:articles)
#articles = #row.sort! {|a,b| a.created_at <=> b.created_at}
This might work better, assuming you've got the relations set correctly between Follow and Article.
class Follow < ActiveRecord::Base
has_many :articles
end
class Article < ActiveRecord::Base
belongs_to :follow, as: blog
end
I think that's right, you'll have to tweak it. http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
With the polymorphic association set up properly, the first line becomes:
#followed_blogs = Blog.
where(follower_id: current_user.id).
includes(:articles)
And if User has the correct association (has_many :blogs or something), it can even become
#articles = current_user.blogs.includes(:articles).
map(&:articles).
sort! {|a,b| a.created_at <=> b.created_at}
#f_blogs = Follow.where('followable_type == ?',"Blog").where('follower_id == ?', current_user.id).pluck('followable_id')
#articles = Article.having(blog_id: #f_blogs).group('id')
Related
I want to create a class method for a class inherits ActiveRecord:Base.
What the method need to do is add where clauses based on the options and it works well.
class Article < ActiveRecord::Base
def self.list_by_params(params={})
articles = self
articles = articles.where(author_id: params[:author_id]) unless params[:author_id].blank?
articles = articles.where(category_id: params[:category_id]) unless params[:category_id].blank?
articles = articles.where("created_at > ?", params[:created_at].to_date) unless params[:created_at].blank?
articles
end
end
This code works fine in case of the call such as:
articles = Article.list_by_params({author_id: 700})
#=> Works fine as I expected.
articles = Article.joins(:authors).list_by_params({author_id: 700})
#=> Works fine as I expected.
However, the problem is that, if I want to call the list_by_params without filtering params, then it lose its former relations. For example:
articles = Article.joins(:authors).list_by_params({})
#=> articles is just a `Article` (not an ActiveRecord_Relation) class itself without joining :authors.
Is there any chance that I made a mistake?
Thanks in advance.
What you are looking for is a scope.
I would do something like this
scope :for_author, lambda { |author| where(author_id: author) unless author.blank? }
scope :in_category, lambda { |category| where(category_id: category) unless category.blank? }
scope :created_after, lambda { |date| where('created_at > ?', date.to_date) unless date.blank? }
scope :list_by_params, lambda do |params|
for_author(params[:author_id])
.in_category(params[:category_id])
.created_after(params[:created_at])
end
Now you can reuse the components of your query. Everything has a names and it gets easier to read the code.
For the self explanation, I've solved the problems by using where(nil).
Actually, Model.scoped returned anonymous scope but the method has been deprecated since Rails version 4. Now, where(nil) can replace the functionality.
class Article < ActiveRecord::Base
def self.list_by_params(params={})
articles = where(nil) # <-- HERE IS THE PART THAT I CHANGED.
articles = articles.where(author_id: params[:author_id]) unless params[:author_id].blank?
articles = articles.where(category_id: params[:category_id]) unless params[:category_id].blank?
articles = articles.where("created_at > ?", params[:created_at].to_date) unless params[:created_at].blank?
articles
end
end
I'm have a few collections in a controller like so:
def show
#cave = Cave.includes(cubs: :mamabear).where(id: params[:id]).first
#cubs = #cave.cubs
#papabear = Papabear.first
#dens = Den.where(papabear_id: #papabear.id)
end
and now I'm trying to sort cubs by dens so I can display the cubs with
#dens.each do |d|
d.cubs
end
so I wrote the following:
def show
....
#dens.each do |den|
den.cubs = [] ## < --- Den does not have an association with Cub ##
#cubs.each do |cub|
den.cubs << cub if cub.mamabear.den_id == den.id
end
end
#reject dens if they don't have cubs
#dens = #dens.reject { |den| den.cubs.all?(&:blank?) }
end
But now I'm getting an undefined method 'cubs for Den' error because Den doesn't have an association with Cub. How do I assign an array of Cubs to each Den without an association?
1: I would create a standard association of Cubs for den. Probably a standard has_many On dens, so each cub has a den_id.
You're messing about re-inventing the wheel, otherwise.
2: you'll probably find that the undefined method is 'cubs=' as opposed to 'cubs'. It's an important distinction as it says what the code is doing when the error is thrown.
3: if you really want to ignore point 1 and make your own which fills an arbitrary attribute from the controller, you can add this to the Den model.
attr_accessor :cubs
Association is the best way to handle such scenarios if you want to fetch those cubs belonging to den at multiple places. if you dont want to implement association. you can try this solution
#den_cubs = []
#dens.each do |den|
cub_for_den= {} #here we are initializing hash each time for new den
#cubs.each do |cub|
cub_for_den[cub.id] = cub if cub.mamabear.den_id == den.id
end
#den_cubs << cub_for_den #put the hash in array
end
#den_cubs = #den_cubs.reject { |dc| dc.blank? }
on the show page you can do this
#den_cubs.each do |dc|
dc.each do |k,v|
# now here you can display all attributes for the cubs
end
end
Have you considered using a "has many through"-association in regards to cubs -> dens and dens -> cubs?
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Examples:
class Den
has_many :mamabears
has_many :cubs, through: :mamabears
end
class Cup
belongs_to :mamabear
has_one :den, through: :mamabear
end
Then you should be able to do something like:
den.cups #=> [<Cup ...>]
cup.den #=> <Den ...>
Say, I have a method called posted_listings, which is supposed to run an ActiveRecord query and return a collection of User.listings where posted: true, with posted? being a Listing class method. So far I have been doing:
class Bar < ActiveRecord::Base
def posted_listings
posted_listings = []
listings.each { |listing| posted_listings << listing if listing.posted? }
posted_listing
end
end
but each time this query runs I start feeling really bad about my skills (or lack of thereof). What is the most efficient way to return a collection of posted listings?
Edit:
posted? is not an attribute, its a class method:
class Listing < ActiveRecord::Base
def posted?
true if quantity >= 1 && has_sellers?
end
end
def has_sellers?
sellers.count >=1 #association to Seller
end
I would recommend adding a scope to your Listing model like this:
scope :posted, -> { where(posted: true) }
Then you can get all posted listings like this:
#user.listings.posted
You can learn more about scopes here if you are interested.
UPDATE
Try this scope instead:
def self.posted
joins(:sellers)
.where('posted = ? AND quantity > ?', true, 0)
.group('listings.id')
.having('COUNT(sellers.id) > ?', 0)
end
Your question is not so clear for me.
You may try:
User.listings.where(posted: true)
to get all users' posted Listings.
Or, saying #useris an User instance:
#user.listings.where(posted: true)
to get posted Listings from an specific user.
I want to grab the ratings from movies in the database and return an array of unique ratings, sorted in the following order:
G PG PG-13 R NC-17
A plain Array#sort wasn't enough:
["PG-13", "PG", "NC-17", "G", "R"].sort
# => ["G", "NC-17", "PG", "PG-13", "R"]
The following code gives me what I want, but seems like there's a better way to write it not having to use delete and <<. Any ideas would be appreciated.
class Movie < ActiveRecord::Base
def self.all_ratings
allRatings = []
Movie.all.each do |movie|
unless allRatings.include?(movie.rating)
allRatings << movie.rating
end
end
if allRatings.include?("NC-17")
allRatings.sort!
allRatings.delete("NC-17")
allRatings << "NC-17"
return allRatings
else
return allRatings.sort
end
end
end
UPDATE:
Using Sergio's tip, I was able to refactor the code. If anyone has some other ideas, I'd appreciate the feedback.
class Movie < ActiveRecord::Base
def self.all_ratings
allRatings = []
Movie.all.each do |movie|
unless allRatings.include?(movie.rating)
allRatings << movie.rating
end
end
allRatings.sort_by! {|t| t == 'NC-17' ? 'ZZZ' : t}
end
end
UPDATE:
Using ByScripts tip, this code works well and is very concise. I had to upgrade from Rails 3.1.0 to Rails 3.2.8 to get the pluck method. Looks like it was introduced in 3.2.1.
I also had to add .sort to get the desired output.
class Movie < ActiveRecord::Base
def self.all_ratings
all_ratings = Movie.pluck(:rating).uniq.sort# don't retrieve unnecessary datas
all_ratings << all_ratings.delete('NC-17') # directly inject NC-17 at the end if exists
all_ratings.compact # remove nil values
end
end
You can use this small trick
sorted = ["PG-13", "PG", "NC-17", "G", "R"].sort_by {|t| t == 'NC-17' ? 'ZZZ' : t }
sorted # => ["G", "PG", "PG-13", "R", "NC-17"]
Basically, for sorting purposes, you substitute "NC-17" with a "ZZZ" which sorts last.
That should works :
def self.all_ratings
all_ratings = Movies.order(:rating).pluck(:rating).uniq # don't retrieve unnecessary datas
all_ratings << all_rating.delete('NC-17') # directly inject NC-17 at the end if exists
all_ratings.compact # remove nil values
end
You can also do Movies.uniq.pluck(:rating)
That does a SELECT DISTINCT query (where pluck.uniq filters the array). Don't know if there is a performance impact (maybe a lower memory footprint ?).
Anyway, both should works the same.
I like Sergio's trick, but if you're looking for a simpler version of your original code that still does have delete and <<, try this
def sort(ratings)
ratings.sort!
return ratings unless ratings.include?("NC-17")
ratings.delete("NC-17")
ratings << "NC-17"
end
class Movie < ActiveRecord::Base
RatingOrder = %w[G PG PG-13 R NC-17]
def self.all_ratings
RatingOrder & Movie.all.map(&:rating)
end
end
Taking following association declaration as an example:
class Post
has_many :comments
end
Just by declaring the has_many :comments, ActiveRecord adds several methods of which I am particularly interested in comments which returns array of comments. I browsed through the code and following seems to be the callback sequence:
def has_many(association_id, options = {}, &extension)
reflection = create_has_many_reflection(association_id, options, &extension)
configure_dependency_for_has_many(reflection)
add_association_callbacks(reflection.name, reflection.options)
if options[:through]
collection_accessor_methods(reflection, HasManyThroughAssociation)
else
collection_accessor_methods(reflection, HasManyAssociation)
end
end
def collection_accessor_methods(reflection, association_proxy_class, writer = true)
collection_reader_method(reflection, association_proxy_class)
if writer
define_method("#{reflection.name}=") do |new_value|
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
association = send(reflection.name)
association.replace(new_value)
association
end
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
ids = (new_value || []).reject { |nid| nid.blank? }
send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
end
end
end
def collection_reader_method(reflection, association_proxy_class)
define_method(reflection.name) do |*params|
force_reload = params.first unless params.empty?
association = association_instance_get(reflection.name)
unless association
association = association_proxy_class.new(self, reflection)
association_instance_set(reflection.name, association)
end
association.reload if force_reload
association
end
define_method("#{reflection.name.to_s.singularize}_ids") do
if send(reflection.name).loaded? || reflection.options[:finder_sql]
send(reflection.name).map(&:id)
else
send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)
end
end
end
In this sequence of callbacks, where exactly is the actual SQL being executed for retrieving the comments when I do #post.comments ?
You need to dig deeper into the definition of HasManyAssociation.
colletion_reader_method defines a method called comments on your Post class. When the comments method is called, it ensures there's a proxy object of class HasManyAssociation stored away (you'll need to dig into the association_instance_set method to see where exactly it stores it), it then returns this proxy object.
I presume the SQL comes in when you call a method on the proxy, for example, calling each, all or accessing an index with [].
Here you are: a standard AR query getting all the ids of the associated objects
send(reflection.name).all(:select => "#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map(&:id)
but sure Activerecord is messy... a re-implementation (better without eval) of has_many maybe can be useful for you:
def has_many(children)
send(:define_method, children){ eval(children.to_s.singularize.capitalize).all( :conditions => { self.class.name.downcase => name }) }
end
In the association reader the line
association = association_proxy_class.new(self, reflection)
in the end will be responsible for executing the find, when the instance variable is "asked" for and "sees" that #loaded is false.
I am not 100% sure I understand what you are looking for.
The sql generation is not in one place in AR. Some of the database specific things are in the database "connection_adapters".
If you are looking for the way how the records are found in the database, look at the methods "construct_finder_sql" and "add_joins" in the ActiveRecord::Base module.
def construct_finder_sql(options)
scope = scope(:find)
sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
add_joins!(sql, options[:joins], scope)
...
and
def add_joins!(sql, joins, scope = :auto)
scope = scope(:find) if :auto == scope
merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
case merged_joins
when Symbol, Hash, Array
if array_of_strings?(merged_joins)
sql << merged_joins.join(' ') + " "
else
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
end
when String
sql << " #{merged_joins} "
end
end
I hope this helps!