I have a Ticket.rb model, and School.rb model.
Ticket belongs_to School
School has_many Tickets
How can I use ActiveRecord to find out total .count of how many Tickets each School has?
I'm aware that Ticket.where(:school_id => -insert School ID here-).count will give me the individual count of each school but I'm using this to populate the data into a graph so I really need something more like School.tickets.count.
Is this possible without messing up my associations?
Thanks all..
Ticket.group(:school_id).count
This will give you the count of tickets for each school id with key as the school_id and the value as the ticket count
If you want to group by a different attribute on School, then
Ticket.joins(:school).group("schools.name").count
Eg output:
{3 => 10, 4 => 30}
If you have a specific School instance (such as #school), you can use
#school.tickets.count.
For better performance, you can use the group method in your controller outlined here.
So in your controller:
def someaction
#school_tickets = Ticket.group(:school_id)
end
and in your view you can loop through the #school_tickets such as:
#school_tickets.each do |school_ticket|
school_ticket.count
end
Related
I'm fairly new to rails and I'm trying to make a database app for a school as practice. So here's the ERD that I constructed:
What I want to have is a list of students under a school. I know how to retrieve the teachers under a school and the students under the teacher it's basically just school.teachers and teacher.students but I don't know how to get what school -> students.
You can setup a has many through relation:
class Teacher < ApplicationRecord
belongs_to :school
has_many :students
end
class School < ApplicationRecord
has_many :teachers
has_many :students, through: :teachers
end
And then just do
school = School.find(some_id)
students = school.students
Some notes:
Foreign keys in Rails are usually of the format something_id, so instead of TeacherId you would use teacher_id. Primary keys are usually just called id. Of course you can call them whatever you want, things just require less configuration if you go with the defaults.
Unless this is just to play around with Rails I'd probably change the relation between teacher and students to a many to many. In this case you'd need to distinct the students:
has_many :students, -> { distinct }, through: :teachers
UPDATE:
Many To Many
Yes, you are right. To have a many to many relation you will need a "join table". This is not mandated by Rails though, but the way relational DBs work. Perhaps it's best if you start a new question if you need help with goign that route.
through
:through is an option you can pass to has_many. It is described here
https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association (altough they use a "has and belongs to many" association in the example)
In your example I think of it like: "Hey school, please give me all students that you can reach via the teachers"
And ActiveRecord will create a query similiar to this:
school = School.first
school.students.to_sql
=> "SELECT \"students\".* FROM \"students\" INNER JOIN \"teachers\" ON \"students\".\"teacher_id\" = \"teachers\".\"id\" WHERE \"teachers\".\"school_id\" = 1"
(where 1 is the ID of the school you called the students method on).
If you do the query as suggested by #TTD then it will most likely result in something like this:
school = School.first
Student.where(teacher: Teacher.where(school: s)).to_sql
=> "SELECT \"students\".* FROM \"students\" WHERE \"students\".\"teacher_id\" IN (SELECT \"teachers\".\"id\" FROM \"teachers\" WHERE \"teachers\".\"school_id\" = 1)"
Which is working as well, but uses a nested query to obtain the teacher ids.
There is yet another way that I see used from time to time:
school = School.first
teacher_ids = school.teachers.pluck(:id) # only select the teacher ids
students = Student.where(teacher_id: teacher_ids)
I'd not recommend to do it this way though. It will fire two queries to get the students and transfer back and forth more data:
one to get all the teacher ids
one to get all the students belonging to to those teacher ids
Student.where(teacher: Teacher.where(school: school))
A Miniatures model has many Collections. Users can have and vote for the best Collection version of a Miniature. The votes are in a model called Imagevotes which update a counter_cache attribute in the Collections model.
What I want to do is flag Collections which are ranked first for a Miniature as GOLD, then rank the 2nd, 3rd and 4th as SILVER. I realise I can do this on the Miniature model by selecting the #miniature.collection.first, but I would like to be able to store that like you would store the vote-count in a counter_cache so that I could display the total number of GOLDS or SILVERS for any one user.
Is there a way that each model could have Boolean fields called GOLD and SILVER which would be updated as new votes are cast in the same way that a counter_cache is updated?
Any pointers and further reading much appreciated.
Update:
It occurs to me that this could also be done with a sort of second index column. A vote_position column if you will, that updated with a number from "1" for the record with the highest counter_cache number and ascended from there. Then I could use #miniature.collection.where(:vote_position => "1") or similar. Perhaps this is more ideal?
As it seems for me you just need to implement method in Miniature model:
def set_gold_and_silver
top_collections = self.collections.order("imagevotes_count desc").limit(4)
gold = top_collections.shift
gold.update_attribute :is_gold, true if gold
top_collections.each {|s| s.update_attribute :is_silver, true}
end
after that you can add it to after_create filter of Imagevotes model:
class Imagevotes < ActiveRecord::Base
after_create :set_gold_and_silver
def set_gold_and_silver
self.miniature.set_gold_and_silver
end
end
I am using Rails v2.3.2.
I have a model called UsersCar:
class UsersCar < ActiveRecord::Base
belongs_to :car
belongs_to :user
end
This model mapped to a database table users_cars, which only contains two columns : user_id, car_id.
I would like to use Rails way to count the number of car_id where user_id=3. I konw in plain SQL query I can achieve this by:
SELECT COUNT(*) FROM users_cars WHERE user_id=3;
Now, I would like to get it by Rails way, I know I can do:
UsersCar.count()
but how can I put the ...where user_id=3 clause in Rails way?
According to the Ruby on Rails Guides, you can pass conditions to the count() method. For example:
UsersCar.count(:conditions => ["user_id = ?", 3])
will generates:
SELECT count(*) AS count_all FROM users_cars WHERE (user_id = 3)
If you have the User object, you could do
user.cars.size
or
user.cars.count
Another way would be to do:
UserCar.find(:user_id => 3).size
And the last way that I can think of is the one mentioned above, i.e. 'UserCar.count(conditions)'.
With the belogngs to association, you get several "magic" methods on the parent item to reference its children.
In your case:
users_car = UsersCar.find(1) #=>one record of users_car with id = 1.
users_car.users #=>a list of associated users.
users_car.users.count #=>the amount of associated users.
However, I think you are understanding the associations wrong, based on the fact that your UsersCar is named awkwardly.
It seems you want
User has_and_belongs_to_many :cars
Car has_and_belongs_to_manu :users
Please read abovementioned guide on associations if you want to know more about many-to-many associations in Rails.
I managed to find the way to count with condition:
UsersCar.count(:condition=>"user_id=3")
I am starting to create my sites in Ruby on Rails these days instead of PHP.
I have picked up the language easily but still not 100% confident with associations :)
I have this situation:
User Model
has_and_belongs_to_many :roles
Roles Model
has_and_belongs_to_many :users
Journal Model
has_and_belongs_to_many :roles
So I have a roles_users table and a journals_roles table
I can access the user roles like so:
user = User.find(1)
User.roles
This gives me the roles assigned to the user, I can then access the journal model like so:
journals = user.roles.first.journals
This gets me the journals associated with the user based on the roles. I want to be able to access the journals like so user.journals
In my user model I have tried this:
def journals
self.roles.collect { |role| role.journals }.flatten
end
This gets me the journals in a flatten array but unfortunately I am unable to access anything associated with journals in this case, e.g in the journals model it has:
has_many :items
When I try to access user.journals.items it does not work as it is a flatten array which I am trying to access the has_many association.
Is it possible to get the user.journals another way other than the way I have shown above with the collect method?
Hope you guys understand what I mean, if not let me know and ill try to explain it better.
Cheers
Eef
If you want to have user.journals you should write query by hand. As far as I know Rails does has_many :through associations (habtm is a kind of has_many :through) one level deep. You can use has_many with finder_sql.
user.journals.items in your example doesn't work, becouse journals is an array and it doesn't have items method associated. So, you need to select one journal and then call items:
user.journals.first.items
I would also modify your journals method:
def journals
self.roles(:include => :journals).collect { |role| role.journals }.flatten.uniq
end
uniq removes duplicates and :inlcude => :journals should improve sql queries.
Similar question https://stackoverflow.com/questions/2802539/ruby-on-rails-join-table-associations
You can use Journal.scoped to create scope with conditions you need. As you have many-to-many association for journals-roles, you need to access joining table either with separate query or with inner select:
def journals
Journal.scoped(:conditions => ["journals.id in (Select journal_id from journals_roles where role_id in (?))", role_ids])
end
Then you can use user.journals.all(:include => :items) etc
This is my first post on Stack, so please bear with me if I breach any protocol.
I'm working on a project in Rails (2.1.2), and I have a relations scenario that looks like this:
event [has_many] days
People (in different categories) can sign up for event days, giving the following binding results:
category [has_many] customers [has_many] orders [has_many] days
[belongs_to] event
Now, I'd like to have the total number of 'events' for one customer, or for all customers in a certain category, and I'm stuck. AFAIK, there's no way of performing a simple 'find' through an array of objects, correct? So, what would be the solution; nested loops, and a collect method to get the 'events' from the 'days' in orders?
Please let me know if I'm unclear.
Thanks for your help!
I would personally do this using a MySQL statement. I don't know for sure, but I think it is a lot faster then the other examples (using the rails provided association methods).
That means that in the Customer model you could do something like:
(Note that I'm assuming you are using the default association keys: 'model_name_id')
class Customer
def events
Event.find_by_sql("SELECT DISTINCT e.* FROM events e, days d, orders o, customers c WHERE c.id=o.customer_id AND o.id=d.order_id AND e.id=d.event_id")
end
end
That will return all the events associated with the user, and no duplicated (the 'DISTINCT' keyword makes sure of that). You will, as with the example above, lose information about what days exactly the user signed up for. If you need that information, please say so.
Also, I haven't included an example for your Category model, because I assumed you could adapt my example yourself. If not, just let me know.
EDIT:
I just read you just want to count the events. That can be done even faster (or at least, less memory intensive) using the count statement. To use that, just use the following function:
def event_count
Event.count_by_sql(SELECT DISTINCT COUNT(e.*) FROM ... ... ...
end
Your models probably look like this:
class Category
has_many :customers
class Customer
has_many :orders
has_many :days, :through => :orders # I added this
belongs_to :category
class Order
has_many :days
belongs_to :customer
class Day
belongs_to :event
belongs_to :order
class Event
has_many :days
With this you can count events for customer:
events = customer.days.count(:group => 'event_id')
It will return OrderedHash like this:
#<OrderedHash {1=>5, 2=>13, 3=>0}>
You can get events count by:
events[event_id]
events[1] # => 5
events[2] # => 13
etc.
If you want total number of uniq events:
events.size # => 3
In case of counting events for all customers in category I'd do something like this:
events = {}
category.customers.each {|c| events.merge!(c.days.count(:group => 'event_id') ) }
events.size # => 9 - it should be total number of events
But in this case you lose information how many times each event appeared.
I didn't test this, so there could be some mistakes.