Rails with ActionMailer and complex activerecord (joins, where, and) - ruby-on-rails

I would like to send an email when a project is registered (with a category and a eligible audience) to each user who has created an alert (with the same category and same eligible audience).
MODEL
class Project
belongs_to :category
belongs_to :fondation
has_many :project_eligibles
has_many :eligibles, through: :project_eligibles
end
class Category
has_many :projects
has_many :alerts
end
class ProjectEligible
belongs_to :project
belongs_to :eligible
end
class Alert
belongs_to :user
belongs_to :category
belongs_to :eligible
end
MAILER
class ProjectMailer < ApplicationMailer
def newproject(project)
#project = project
mail(
to: mails = User.joins(:alerts).where(alerts: {category_id: project.category_id}).collect(&:email).join(","),
subject: "New project for you !"
)
end
end
CONTROLLER
class ProjectsController < ApplicationController
def create
#project = Project.new(project_params)
if #project.save
ProjectMailer.newproject(#project).deliver_now
redirect_to projects_path
else
render :new
end
end
end
it works with the category but I can't do it with the eligible audience (many_to_many association) :
def newproject(project)
#project = project
mail(
to: mails = User.joins(:alerts).where(alerts: {eligible_id: project.project_eligibles.where(:eligible_ids)}).collect(&:email).join(","),
subject: "New project for you !"
)
end
end
And more difficult, I don't know how to do it with the 2 conditions ??
Does anyone have an idea to test?
A thousand thanks in advance for your help!

Try this in your ProjectMailer
project_elegibles = ProjectEligible.whare("project_id =?", #project.id).pluck(:eligible_id)
User.includes(:alerts).where("alerts.category_id =? and alerts.eligible_id IN (?)", #project.category_id, project_elegibles).pluck(:email)
Understand steps that i have used to deal with many-to-many relationship. I can't test this in my machine as I don't have the code. But this will helpful to fix your problem.

Assuming project = #project
mails = User.joins(:alerts).where("alerts.eligible_id IN (?) AND alerts.category_id = ?", project.project_eligibles.pluck(:eligible_id), project.category_id).pluck(:"users.email")
Possibly you should use left_outer_join
For rails 5.x.x
mails = User.left_outer_joins(:alerts).where("alerts.eligible_id IN (?) AND alerts.category_id = ?", project.project_eligibles.pluck(:eligible_id), project.category_id).pluck(:"users.email")

Related

Validation in activeadmin with custom controller action

In our web app, a composition has many authors through a table named contributions. We want to check that an admin does not accidentally delete all the authors of one composition in activeadmin (at least one should remain). If this happens, the update fails with an error message and the edit view for a composition is rendered again.
Calling the model validation with
validates_presence_of :authors, on: :update
is not suitable here, because the addition of new contributions (thus authors) is done while calling the success.html on the update function of the activeadmin controller, to prevent some previous bugs that created double entries for authors.
Models are:
class Composition < ApplicationRecord
has_many :contributions
has_many :authors, through: :contributions
end
----
class Contribution < ApplicationRecord
belongs_to :composition
belongs_to :author
end
----
class Author < ApplicationRecord
has_many :author_roles, dependent: :delete_all
has_many :contributions
has_many :compositions, through: :contributions
end
Our code in admin has some background logic to handle what has been described before:
ActiveAdmin.register admin_resource_name = Composition do
...
controller do
def update
author = []
contribution_delete = []
params[:composition][:authors_attributes].each do |number, artist|
if artist[:id].present?
if artist[:_destroy] == "1"
cont_id = Contribution.where(author_id: artist[:id],composition_id: params[:id]).first.id
contribution_delete << cont_id
end
else
names = artist[:full_name_str].strip.split(/ (?=\S+$)/)
first_name = names.size == 1 ? '' : names.first
exist_author = Author.where(first_name: first_name, last_name: names.last, author_type: artist[:author_type]).first
author << exist_author.id if exist_author.present?
end
end if params[:composition][:authors_attributes] != nil
params[:composition].delete :authors_attributes
update! do |success, failure|
success.html do
if author.present?
author.each do |id|
Contribution.create(author_id: id, composition_id: params[:id])
end
end
if contribution_delete.present?
contribution_delete.each do |id|
Contribution.find(id).destroy
end
end
...
redirect_to admin_composition_path(#composition.id)
end
failure.html do
render :edit
end
end
end
end
...
end
Do you have any idea how I can control the authors_attributes and throw a flash message like "There must be at least one author" if the number of to-be-deleted authors is equal to the number of existing authors?
I thought maybe it's possible to handle this before the update! call so to convert the success into a failure somehow, but I have no idea how.

Rails validate associated models

I am trying to prevent users from creating duplicate chatrooms (chatroom includes 2 users). But I have no idea how to validate if chatroom with the same users already exists before save.
Thats my create method in chatroom controller:
def create
#chatroom = Chatroom.new
#friend = User.where(username: params[:friend]).last
#chatroom.chatroom_users.build(user: #friend, chatroom: #chatroom)
#chatroom.chatroom_users.build(user: current_user, chatroom: #chatroom)
if #chatroom.save
flash[:notice] = "chatrooom created"
redirect_to #chatroom
else
flash[:notice] = "chatrooom not created lol"
redirect_to authenticated_root_path
end
end
And that is how I am trying to validate if there is no chatroom with 2 users like the new one:
In my class Chatroom
after_save :duplicate?
# Checks if chatroom containing specific users exists.
def duplicate?
user = self.users.first
friend = self.users.second
# lines below work fine - they check if there is already such chatroom
(Chatroom.all - [self]).each do |chatroom|
if ((chatroom.users & [user, friend]) - [user, friend]).empty?
self.errors.add(:chatroom, "Such chatroom already exists.")
end
end
end
The problem is: if I use after_save in validating method I can get the self.users.first to set user and friend variables, but then It does not stop from creating that record and I am not sure If deleting it there is a good idea. Secondly - I use validate instead of after_save self.users.first and self.users.second returns nil, so I can't check for duplicates.
PS: I do not want to have users id as the attributes in the chatrooms table because I want to add ability to connect to chat for as many ppl as you want.
How about something like this?
def duplicate?
is_duplicate = (Chatroom.all.to_a - [self]).any? do |chatroom|
chatroom.users.map(&:id).sort == self.chatroom_users.map(&:user_id).sort
end
if is_duplicate
errors.add(:chatroom, "Such chatroom already exists.")
end
end
Here are all of the models.
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
class Chatroom < ApplicationRecord
has_many :chatroom_users, :dependent => :destroy
has_many :users, :through => :chatroom_users
before_validation :duplicate?
def duplicate?
is_duplicate = (Chatroom.all.to_a - [self]).any? do |chatroom|
chatroom.users.map(&:id).sort == self.chatroom_users.map(&:user_id).sort
end
if is_duplicate
errors.add(:chatroom, "Such chatroom already exists.")
end
end
end
class ChatroomUser < ApplicationRecord
belongs_to :chatroom
belongs_to :user
end
class User < ApplicationRecord
has_many :chatroom_users, :dependent => :destroy
has_many :chatrooms, :through => :chatroom_users
end
And here is a test
require 'test_helper'
class ChatroomTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
test 'does not allow duplicate chatrooms' do
first_user = User.create!
second_user = User.create!
chatroom = Chatroom.create!
chatroom.chatroom_users.build(user: first_user, chatroom: chatroom)
chatroom.chatroom_users.build(user: second_user, chatroom: chatroom)
chatroom.save!
duplicate_chatroom = Chatroom.create!
duplicate_chatroom.chatroom_users.build(user: first_user, chatroom: chatroom)
duplicate_chatroom.chatroom_users.build(user: second_user, chatroom: chatroom)
assert !duplicate_chatroom.valid?
end
end
Note: This code is in Rails 5.

rails add record to has_many :through join table

class EventTeam < ActiveRecord::Base
belongs_to :event
belongs_to :team
end
class Event < ActiveRecord::Base
has_many :event_teams
has_many :teams, through: :event_teams
end
class Team < ActiveRecord::Base
has_many :event_teams
has_many :events, through: :event_teams
end
I am trying to add the :event_id and :team_id to the EventTeam join table when creating a new Event and can't seem to figure out how, despite an exhaustive search of similar questions such as: how to add records to has_many :through association in rails (I've tried all of these suggestions)
It seems that the following should work, though a NoMethodError is delivered: "undefined method `events' for #ActiveRecord::Relation []"
EventsController
def new
#event = Event.new(:team_id => params[:team_id])
end
def create
#team = Team.where(:id => params[:team_id])
#event = #team.events.create(event_params)
if #event.save
flash[:success] = "Event created!"
redirect_to #event
else
render 'new'
end
end
I have a similar situation in the same app with Users, Teams, and Memberships (join table). The following code automatically adds the :team_id and :user_id to the Memberships table when a user creates a new Team.
TeamsController
def new
#team = Team.new(:user_id => params[:user_id])
end
def create
#team = current_user.teams.create(team_params)
if #team.save
flash[:success] = "Team created!"
redirect_to #team
else
render 'new'
end
end
Any suggestions on how to accomplish this?
undefined method `events' for #ActiveRecord::Relation []
where returns an AR relation not a single instance, so #team.events won't work. Use find instead
#team = Team.find(params[:team_id])
#event = #team.events.create(event_params)
Update
could not find Team with 'id'=
You are getting team_id inside event hash, so params[:team_id] won't work. You need to use params[:event][:team_id]
#team = Team.find(params[:event][:team_id])
#event = #team.events.create(event_params)
Just specify first value of the relation, since you are searching by unique index with value id, so that should be well:
#team = Team.where(id: params[:team_id]).first
#event = #team.events.create(event_params)
That is because .where, unlike find_by or find(1) returning a Relation, not a first value in it.
However, in modern version of rails I saw recommendation to use exactly where.first pair, not a find.

Creating a new object when creating another

I have 3 models: account, player_team and team. Player team serves to associate accounts and teams. Player_team table has account_id and team_id attributes. When I create the team, i should at least have the account who created it belonging to the team. What am i doing wrong ?Any help would be appreciated, Thanks.
def create
#team = Team.new(team_params)
#team.save
#team_player = current_account.player_teams.build(:account_id => current_account.id, :team_id => #team.id)
#team_player.save
respond_with(#team)
end
class Account < ActiveRecord::Base
has_many :player_teams
has_many :teams, through: :player_teams
class Team < ActiveRecord::Base
has_many :player_teams
has_many :accounts, through: :player_teams
end
class PlayerTeam < ActiveRecord::Base
belongs_to :account
belongs_to :team
end
Because you are creating the object right into the controller (instead of just declaring it and opening a form in the view to enter parameters) , you have to use the
new
keyword.
A solution of your problem would be
#team_player = current_account.player_teams.new(:account_id => current_account.id, :team_id => #team.id)
This should work:
def create
#team = Team.new(team_params)
#team.save
#team_player = current_account.build_player_team(:account_id => current_account.id, :team_id => #team.id)
#team_player.save
respond_with(#team)
end
Build on it's own won't save, and saving the parent won't do anything. You need to use build_player_team, or use create() instead of build. Either would work.
def create
#team = Team.new(team_params)
#team.save
#team_player = current_account.player_teams.create(:account_id => current_account.id, :team_id => #team.id)
#team_player.save
respond_with(#team)
end
Note that there's no need to go through all this trouble manually. You could have just said:
respond_with(#team = current_account.teams.create(team_params))

remove specific user from joined table

In Ruby on Rails I have a user models and a jobs model joined through a different model called applicants. I have a button for the users when they want to "remove their application for this job" but I don't know how to remove the specific user, and for that matter I don't know if I'm doing a good job at adding them either (I know atleast it works).
user.rb
class User < ActiveRecord::Base
...
has_many :applicants
has_many:jobs, through: :applicants
end
job.rb
class Job < ActiveRecord::Base
...
has_many :applicants
has_many:users, through: :applicants
end
applicant.rb
class Applicant < ActiveRecord::Base
belongs_to :job
belongs_to :user
end
when someone applies for a job my jobs controller is called:
class JobsController < ApplicationController
...
def addapply
#job = Job.find(params[:id])
applicant = Applicant.find_or_initialize_by(job_id: #job.id)
applicant.update(user_id: current_user.id)
redirect_to #job
end
...
end
Does that .update indicate that whatever is there will be replaced? I'm not sure if I'm doing that right.
When someone wants to remove their application I want it to go to my jobs controller again but I'm not sure what def to make, maybe something like this?
def removeapply
#job = Job.find(params[:id])
applicant = Applicant.find_or_initialize_by(job_id: #job.id)
applicant.update(user_id: current_user.id).destroy
redirect_to #job
end
does it ave to sort through the list of user_ids save them all to an array but the one I want to remove, delete the table then put them all back in? I'm unsure how this has_many works, let alone has_many :through sorry for the ignorance!
thanks!
Let's assume the user will want to remove their own application. You can do something like this:
class UsersController < ApplicationController
def show
#applicants = current_user.applicants # or #user.find(params[:id]), whatever you prefer
end
end
class ApplicantsController < ApplicationController
def destroy
current_user.applications.find(params[:id]).destroy
redirect_to :back # or whereever
end
end
And in your view:
- #applicants.each do |applicant|
= form_for applicant, method: :delete do |f|
= f.submit
Don't forget to set a route:
resources :applicants, only: :destroy
Some observations, I would probably name the association application instead of applicant. So has_many :applications, class_name: 'Applicant'.

Resources