In the project management app I'm working on, I'm currently working on a page for managing tickets, which I want should contain of the following:
- The tickets that the user has created
- The tickets that belongs to projects that the user has created
The tricky part is to use the right code in the controller, which is where I need help.
'#users_tickets'works fine, as well as '#owned_projects'. However, the last thing which is creating an array that contains of the tickets that belongs projects that the user owns, is something I need help me (yes, I understand that my poor try with an each loop is totally the wrong way to go here).
How can I achieve what I want?
Tickets controller:
1. def manage
2. #users_tickets = Ticket.where(:user_id => current_user.id)
3. #owned_projects = Project.where(:user_id => current_user)
4.
5. #owned_projects.each do |project|
6. #tickets_for_owned_projects = Ticket.where(:project_id => project.id)
7. end
8. end
Tables:
tickets table:
project_id
ticket_status_id
user_id
title
description
start_date
end_date
projects table:
user_id
title
description
start_date
end_date
If you're using a has_many association, then it should as simple as
class User < ActiveRecord::Base
has_many :projects
has_many :tickets
has_many :project_tickets, through: :projects, class_name: 'Ticket', source: :tickets
#...
end
class Project < ActiveRecord::Base
has_many :tickets
#...
end
# in tickets controller
def manage
#tickets = current_user.tickets
#tickets_for_owned_projects = current_user.project_tickets
end
UPD: The approach above should work. I'm literally falling asleep right now and can't define what is wrong here. Would really appreciate if someone looked into it.
Here's another way around though.
class User < ActiveRecord::Base
has_many :projects
has_many :tickets
def project_tickets
result = []
self.projects.each do |project|
result << project.tickets
end
result.flatten
end
#...
end
Related
I have three models in my API-only backend - User, Game and Ownership, which acts as a join table between users and games.
I have a service method that calls a remote API to get data on games the user owns. It then uses the data to add relevant games into the database, and update the user with extra data (game_count) and creates the relationships between user and his games. I'm able to accomplish this like so:
class GameService
def self.getGamesForUser(user)
# response hash gets populated here
games = response[:games].map do |data|
Game.find_or_create_by(app_id: data[:appid]) do |g|
g.name = data[:name]
g.icon = data[:img_icon_url]
g.logo = data[:img_logo_url]
end
end
user.update games: games, game_count: response[:game_count]
end
end
So far, so good. While updating the user, the ownership associations between the user and the games are created automatically. But I would also like to add extra attributes to the join table at the same time (like playtime). I haven't been able to find a nice solution to this. I tried looking at nested_attributes_for, by adding accepts_nested_attributes_for :ownerships to the game model, and calling
g.ownerships_attributes = [{
playtime: data[:playtime_forever]
}]
before updating the user, but that seemed to have no effect whatsoever. I feel like there must be an elegant solution to this, I'm just not seeing it.
Here is the code for my models:
User.rb
class User < ApplicationRecord
has_many :ownerships
has_many :games, through: :ownerships
end
Game.rb
class Game < ApplicationRecord
has_many :ownerships
has_many :owners, through: :ownerships, source: :user
end
Ownership.rb
class Ownership < ApplicationRecord
belongs_to :user
belongs_to :game
end
When iterating through games, save playtimes in a hash (with game.id as key)
games = response[:games].map do |data|
playtimes = Hash.new
game = Game.find_or_create_by(app_id: data[:appid]) do |g|
g.name = data[:name]
g.icon = data[:img_icon_url]
g.logo = data[:img_logo_url]
end
#This is outside the find_or_create_by because it must be done for all games, not just the newly created.
playtimes[game.id] = data[:playtime_forever]
end
Finnaly, after updating games, update playtimes in the join table:
user.ownerships.find_each do |own|
own.update_attributes(:playtime => playtimes[:own.game_id])
end
Given the following model structures;
class Project < ApplicationRecord
has_many :leads
has_and_belonds_to_many :clients
end
class Lead < ApplicationRecord
belongs_to :project
end
class Client < ApplicationRecord
has_and_belongs_to_many :projects
end
How you would suggest reporting on duplicate leads across a Client?
Right now I am doing something very gnarly with flattens and counts, it feels like there should be a 'Ruby way'.
Ideally I would like the interface to be able to say either Client.first.duplicate_leads or Lead.first.duplicate?.
Current (terrible) solution
#duplicate_leads = Client.all.map(&:duplicate_leads).flatten
Class Client
def duplicate_leads
leads = projects.includes(:leads).map(&:leads).flatten
grouped_leads = leads.group_by(&:email)
grouped_leads.select { |_, v| v.size > 1 }.map { |a| a[1][0] }.flatten
end
end
Environment
Rails 5
Ruby 2.3.1
You could try this.
class Client < ApplicationRecord
has_and_belongs_to_many :projects
has_many :leads, through: :projects
def duplicate_leads
duplicate_ids = leads.group(:email).having("count(email) > 1").count.keys
Lead.where(id: duplicate_ids)
end
end
You could try creating a has_many association from Lead through Project back to Lead, in which you use a lambda to dynamically join based on a match on email between the two records, and on the id not matching. This would mark both records as a duplicate -- if you wanted to mark only one then you can require that the id of one is less than the id of the other.
Some hints: Rails has_many with dynamic conditions
I've got a dilemma I think I may have coded myself into a corner over. Here's the setup.
My site has users. Each user has a collection of stories that they post. And each story has a collection of comments from other users.
I want to display on the User's page, a count of the total number of comments from other users.
So a User has_many Stories, and a Story has_many comments.
What I tried was loading all the users stories in #stories and then displaying #stories.comments.count, but I get undefined method 'comments' when I try to do that. Is there an efficient ActiveRecord way to do this?
class User < ActiveRecord::Base
has_many :stories
has_many :comments, through: :stories
end
class Story < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :story
end
Now you should be able to get User.last.comments.count
I think you need to refine this more for a proper labelling.
The quick solution is to iterate over the #stories collection and add the counts up. This is not a purely active record solution though.
totalComments = 0
#stories.each do |story|
totalComments += story.count
end
For a pure active record solution I would need to assume that each has_many association has a corresponding belongs_to association. So a User has_many Stories and a Story belongs_to a User. If that is the case and comments have a similar association to stories then you can search comments by user_id. Something like:
Comments.where("comment.story.user" => "userId")
I hope that helps.
In your controller you should have something like this (note the use of includes):
#user = User.find( params[:id] )
#stories = #user.stories.includes(:comments)
Then in your view you can do something like the following to show the total number of comments for that particular user:
Total number of comments: <%= #stories.map{|story| story.comments.length}.sum %>
I have two models with a many to many relationship using has_and_belongs_to_many. Like so:
class Competition < ActiveRecord::Base
has_and_belongs_to_many :teams
accepts_nested_attributes_for :teams
end
class Team < ActiveRecord::Base
has_and_belongs_to_many :competitions
accepts_nested_attributes_for :competitions
end
If we assume that I have already created several Competitions in the database, when I create a new Team, I would like to use a nested form to associate the new Team with any relevant Competitions.
It's at this point onwards that I really do need help (have been stuck on this for hours!) and I think my existing code has already gone about this the wrong way, but I'll show it just in case:
class TeamsController < ApplicationController
def new
#team = Team.new
#competitions.all
#competitions.size.times {#team.competitions.build}
end
def create
#team = Team.new params[:team]
if #team.save
# .. usual if logic on save
end
end
end
And the view... this is where I'm really stuck so I won't both posting my efforts so far. What I'd like it a list of checkboxes for each competition so that the user can just select which Competitions are appropriate, and leave unchecked those that aren't.
I'm really stuck with this one so appreciate any pointing in the right direction you can provide :)
The has_and_belongs_to_many method of joining models together is deprecated in favor of the new has_many ... :through approach. It is very difficult to manage the data stored in a has_and_belongs_to_many relationship, as there are no default methods provided by Rails, but the :through method is a first-class model and can be manipulated as such.
As it relates to your problem, you may want to solve it like this:
class Competition < ActiveRecord::Base
has_many :participating_teams
has_many :teams,
:through => :participating_teams,
:source => :team
end
class Team < ActiveRecord::Base
has_many :participating_teams
has_many :competitions,
:through => :participating_teams,
:source => :competition
end
class ParticipatingTeam < ActiveRecord::Base
belongs_to :competition
belongs_to :team
end
When it comes to creating the teams themselves, you should structure your form so that one of the parameters you receive is sent as an array. Typically this is done by specifying all the check-box fields to be the same name, such as 'competitions[]' and then set the value for each check-box to be the ID of the competition. Then the controller would look something like this:
class TeamsController < ApplicationController
before_filter :build_team, :only => [ :new, :create ]
def new
#competitions = Competitions.all
end
def create
#team.save!
# .. usual if logic on save
rescue ActiveRecord::RecordInvalid
new
render(:action => 'new')
end
protected
def build_team
# Set default empty hash if this is a new call, or a create call
# with missing params.
params[:team] ||= { }
# NOTE: HashWithIndifferentAccess requires keys to be deleted by String
# name not Symbol.
competition_ids = params[:team].delete('competitions')
#team = Team.new(params[:team])
#team.competitions = Competition.find_all_by_id(competition_ids)
end
end
Setting the status of checked or unchecked for each element in your check-box listing is done by something like:
checked = #team.competitions.include?(competition)
Where 'competition' is the one being iterated over.
You can easily add and remove items from your competitions listing, or simply re-assign the whole list and Rails will figure out the new relationships based on it. Your update method would not look that different from the new method, except that you'd be using update_attributes instead of new.
A user has_many :donations, a project has_many :donations, and a donation belongs_to :user and belongs_to :project.
I'm looking for a sensible way to extract the projects associated with a user (through donations) into an array.
I'm currently doing:
def index
#user = User.find params[:user_id]
#projects = []
#user.donations.each do |donation|
#projects << donation.project
end
end
I feel like I'm missing something obvious, as this seems lame. Is there a better way to do this?
Edit
I accidentally simplified this too far. A user can also be associated with a project through other models, so #projects = #user.projects isn't going to do what I need it to.
class User < AR::Base
has_many :donations
has_many :projects, :through => :donations
…
end
#user.projects
should work.
For gathering many association collections see my previous answer. You will need to adapt it to use the through associations (just treat them as normal has_masnys), but the same applies.