Display categories that have posts in Rails 4 - ruby-on-rails

Trying to display a list of categories that have posts. Sounds simple but i'm a little stuck.
Similar to this
ActiveRecord find all parents that have associated children
class Category < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :category
end
Can you please help. I tried named scope but breaks the app.

You could either use a scope like this
scope :with_posts, -> { includes(:posts).where("posts.id IS NOT NULL") }
or using counter_cache
class Category < ActiveRecord::Base
has_many :posts
scope :with_posts, -> { where("posts_count > ?", 0) }
end
class Post < ActiveRecord::Base
belongs_to :category, counter_cache: true
end
Note that you have to add a posts_count integer field to the categories table for this to work. It is also advisable to save all your categories the first time to populate this field.

Related

Rails activerecords with nested includes

I have the following models:
class BusinessProcess < ActiveRecord::Base
has_many :todos
end
class Todo < ActiveRecord::Base
has_one :row
end
class Row < ActiveRecord::Base
has_many :users
end
How can I count the number of rows in a BusinessProcess that has rows on a specific user?
Something like:
#businessProcess.todos.includes(XXX).where(users.id=?,1).count
#businessProcess.todos.includes(:row => :users).where("users.id=?",1).count
According to your associations, I'd rather go with just joining the tables like:
class Todo < ActiveRecord::Base
has_one :row
has_many: users, through: :row
scope :by_user_id, ->(user_id) {
joins(:users).where("users.id = ?", user_id)
}
end
and then:
#business_process.todos.by_user_id(1).count
Maybe you also could think of moving the where condition into a scope of Row, but that is more a responsibility thingie.
You also could read about ARel as an alternative: The N+1 problem and ARel.

Filtring query in :through association

I have 3 models:
class User < ActiveRecord::Base
has_many :categories
has_many :links, through: :categories
end
class Category < ActiveRecord::Base
belongs_to :user
has_many :links
end
class Link < ActiveRecord::Base
belongs_to :category
end
For given user I want to find all his links which have favorite field equals true. I'm learning rails from 'rails guides' and I searched there for simple query to this, but I didn't find anything. Finally I resolved problem using select iterator:
#links = current_user.links.select{ |l| l.favorite }
But I'm not sure it is a good solution. How do that in rails way?
To add to #Pierre Michard's answer, you may also wish to look at ActiveRecord Association Extensions, which will basically replace the scope in the Link model:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :categories
has_many :links, through: :categories do
def favorites
where favorite: true
end
end
end
This will allow you to call:
#links = current_user.links.favorites
That works fine but the SQL query generated by that code will be something like
SELECT * FROM links where links.user_id = ?
Then the links you're interested in will be filtered by the select method.
If your user has many links and few of them are favorites, it could be more efficient to select the favorites this way:
#links = current_user.links.where(favorite: true)
This will generate this kind of query:
SELECT * FROM links where links.user_id = ? AND links.favorite = 't'
you can either create a scope in your links model to filter favorite links.
class Links < ActiveRecord::Base
scope :favorites, -> { where(favorite: true) }
end
corresponding query:
#links = current_user.links.favorites
This can be more efficient because that will create less ActiveModel objets.
Many to many relationship needs to be,
class User < ActiveRecord::Base
has_many :categories
has_many :links, through: :categories
end
class Category < ActiveRecord::Base
belongs_to :user
belongs_to :link
end
class Link < ActiveRecord::Base
has_many :categories
has_many :users, through: :categories
end
And then you can fetch link records whose favorite column is true as,
#links = current_user.categories.include(:links).where('links.favorite = ?', true)

Is: grandparent.parents.children association chaining not correct in Rails 4?

I'm having trouble figuring out the proper way of retrieving all children of multiple parents through association chaining.
To simplify I have three models:
class Customer < ActiveRecord::Base
has_many :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :invoice
end
After creating a few objects I tired to use the example from rails guides (association basics: 4.3.3.4 includes):
Customer.first.invoices.line_items
It returns:
undefined method `line_items' for #<Customer::ActiveRecord_Associations_CollectionProxy
Is grandparent.parents.children not usable?
EDIT
I'm not searching for the grandparent.parents.first.children, but all children of all parents in the collection, rails guides state:
If you frequently retrieve line items directly from customers (#customer.orders.line_items),
As a valid operation, I would like to know if that is a mistake.
FINAL As stated in the comments of the selected answer: in ActiveRecord: scopes are chainable but associations are not.
The customer.invoices.line_items cannot work the way you want to, since the has_many always is linked to a single record. but you can achieve what you want (if I understand correctly) using has_many through
as follows:
class Customer < ActiveRecord::Base
has_many :invoices
has_many :line_items, through: :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :invoice
end
and now you can write:
customer.line_items
and it will return all line_items which are connected to a customer's invoices.
Customer.first.invoices.first.line_items
Or if you want all of the data together, you can do something like:
results = Customer.first.invoices.includes(:line_items)
Then you may access data with no DB call, by looping results. For first data ex: results.first.line_items
Hope it helps!
Customer.first.invoices will return an collection (like an array) of invoices. The line_items method isn't defined for a collection, but its defined for an invoice. Try Customer.first.invoices.first.line_items
EDIT - If you always want the orders to include the line items, you can just do:
class Customer < ActiveRecord::Base
has_many :orders, -> { includes :line_items }
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
end

Rails 4, Active Record, create order scope on belongs_to for has_many_through

I have the following models set up:
class Location < ActiveRecord::Base
has_many :location_parking_locations
has_many :parking_locations, through: :location_parking_locations
end
class LocationParkingLocation < ActiveRecord::Base
belongs_to :location
belongs_to :parking_location
end
class ParkingLocation < ActiveRecord::Base
has_many :location_parking_locations
has_many :locations, through: :location_parking_locations
end
The LocationParkingLocation has an integer field called upvotes. I would like to create a 'by_votes' scope that I can add to a query to order the results by this upvotes field. Where and how do I define this scope, so that I can call it like this:
location.parking_locations.by_votes
I can't define it like this, because then it's not a valid method on parking_locations:
class LocationParkingLocation < ActiveRecord::Base
belongs_to :location
belongs_to :parking_location
scope :by_votes, -> { order("upvotes DESC") }
end
Should it be defined in the 'ParkingLocation' class? If so, how do I tell it that I want to order by a field on the location_parking_locations table?
I think you might be able to use merge here.
You can leave your scope in the LocationParkingLocation class, and the result would look like:
location.parking_locations.merge(LocationParkingLocation.by_votes)
I just read a little about it in this blog post.

Active Record includes with STI

I have the following model
class Event < ActiveRecord::Base
has_many :attendances
class Attendance < ActiveRecord::Base
belongs_to :user
class Student < User
has_one :student_detail
class StudentDetail < ActiveRecord::Base
belongs_to :school
class Staff < User
has_one :staff_detail
class StaffDetail < ActiveRecord::Base
The StudentDetail and StaffDetails have additional information, I am trying to avoid having it all in one STI user table due to having to work with something similar to concrete class per table pattern
I can do this easily enough
Event.includes(:attendances => :user).where(...)
but I want to be able to includes depending on user type
e.g.
Event.includes(attendances: {:user => :student_details })
This will fail as some of the users are Staff objects.
I realise rails won't support this out of the box, but anyone have any tricks to get this to work
best solution right now would be split user on attendance to student and staff
i.e.
class Attendance < ActiveRecord::Base
belongs_to :student, -> {includes(:staff_detail) }
belongs_to :staff, -> {includes(:student_detail) }
#belong_to :user
which isn't ideal.
Anyone have any tips? way to solve this.
The easiest way is to just move the has_one associations down on to user. Since only Staff records will have staff_details, the preloading will just work.
class User < ActiveRecord::Base
has_one :staff_detail
has_one :student_detail
end
class Staff < User; end
class Student < User; end
That's not ideal though. To customise preloading further, you can use the Preloader class in Rails. First, load all the records without any includes, then iterate over them and preload the associations you need:
events = Event.includes(:attendances => :user)
users = events.users.flatten
users.group_by(&:class).each do |klass, records|
associations = {
Staff: [:staff_detail],
Student: [:student_detail]
}.fetch(klass, [])
ActiveRecord::Associations::Preloader.new(records, associations).run
end
Note that this API changed in Rails 4. In versions 3 and earlier, you just used the preload_associations method.
A while back I wrote a blog post about this same problem which includes a couple of other neat tricks (such as spec'ing that you get correct behaviour).
How about putting the includes on the STI models as a default_scope?
class Event < ActiveRecord::Base
has_many :attendances
class Attendance < ActiveRecord::Base
belongs_to :user
class Student < User
has_one :student_detail
default_scope includes(:student_detail)
class StudentDetail < ActiveRecord::Base
belongs_to :school
class Staff < User
has_one :staff_detail
default_scope includes(:staff_detail)
class StaffDetail < ActiveRecord::Base
Then I think this:
Event.includes(:attendances => :user).where(...)
Should eager load for both Students and Staff.
You could just use named scopes to make your life a little easier.
class Event < ActiveRecord::Base
has_many :attendances
scope :for_students, -> { includes(:attendances => { :users => :student_detail }).where('users.type = ?', 'Student') }
scope :for_staff, -> { includes(:attendances => { :users => :staff_detail }).where('users.type = ?', 'Staff') }
end
Then you can just do Event.for_students

Resources