Changing Data from an other model - ruby-on-rails

I am wondering what kind of query should I accept to allow my data to be updated. My models consists of client, interest, and a manager
Clients his has follow
id
name
email
password
Interest
id
description
manager
customer_id
interest_id
created_at
The goal of the manager his not to override old data in interest but just keep adding a new interest and refering to it.
The relationship his has follow
class Client < ActiveRecord::Base
has_many :music_interest_managers
has_many :music_interests, through => :music_interest_managers
end
class MusicInterest < ActiveRecord::Base
has_many :music_interest_managers
has_many :clients, through => :music_interest_managers
end
class MusicInterestManager < ActiveRecord::Base
belongs_to :music_interests
belongs_to :client
end
Now to update the data from the customer controller i am not sure how would i do this
This is what i am thinking about:
#client = Client.find(params[:id])
#manager = #client.manager.build(params[:manager])
#interest = #interest.manager.build(params[:interest])
Does this make sense? or i am dead wrong?
Update:
def update
#client = Client.find(params[:id])
#interest = #client.music_interests.build(params[:interest])
if #client.update_attributes(params[:client])
flash[:success] = "Profile updated"
#sign_in #client
redirect_to #client
else
render 'edit'
end
end
Or should i render a model view from interest to then apply the change?

#client = Client.find(params[:id])
#interest = #client.music_interests.build(params[:interest])
should work - try it out in the console!

Related

"ActiveModel::ForbiddenAttributesError " on Rails App ( 5) with nested attributes

I have a simple_form_for that creates an invoice. Through this form, I want the user to be able to create a client that will be associated with that before-mentionned invoice. The current process being to firstly create the client, then associate it to the invoice by selecting it from a collection of created clients when the user create an invoice.
My models :
class Client < ApplicationRecord
has_many :invoices
end
class Invoice < ApplicationRecord
belongs_to :client
accepts_nested_attributes_for :client, reject_if: :all_blank, allow_destroy: true
end
Invoice controller:
def new
#invoice = Invoice.new
#invoice.build_client
end
def create
#invoice = Invoice.new(invoice_params)
#client = #invoice.build_client(params[:invoice][:client_attributes])
#client.user = current_user
#client.save
end
And I made sure to update my strong params in Invoice Controller with :
params.require(:invoice).permit(:param1, :param2,client_attributes:[:param1, :param2, :param3, etc..],..)
That being said, when creating an invoice, I ran into an "ActiveModel :: ForbiddenAttributesError", which is set to appears when strong params are not correctly defined. Which, in my case, does not seem to be the case.
I found out that adding "params.permit!" in my #Create in the Invoice Controller, allowed me to avoid that error. But that's a trick. It should not be necessary since that's the jobs of the strong params. Has anyone ever came across a similar case, please?
Ok, so I figured this thing out. All that was needed to do was to - obviously- save my client before, my invoice. Rather simple, isn't it!
Here is my final Invoice #New #Create
def new
#invoice = Invoice.new
#invoice.build_client
end
def create
#invoice = Invoice.new(invoice_params)
#invoice.client.user = current_user
#invoice.client.save
if #invoice.save
redirect_to #invoice
else
render :new
end
end

Rails with ActionMailer and complex activerecord (joins, where, and)

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")

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.

Accessing additional values on has_many through Rails

I am having real trouble accessing the value of an additional parameter called permission on a has_many through. It is probably something simple.
My 3 Models are
class User < ActiveRecord::Base
has_many :players_users
has_many :players, through: :players_users
end
class Player < ActiveRecord::Base
has_many :players_users
has_many :users, through: :players_users
end
class PlayersUser < ActiveRecord::Base
belongs_to :user
belongs_to :player
validates :player_id, uniqueness: { scope: :user_id }
end
My controller saves the record without issue. Adding the permission value to the correct joining table.
def create
#players = Player.new(players_params)
#user= current_user
if #players.save
#player = Player.last
#user.save && #user.players_users.create(:player_id =>#player.id, :permission =>"owner")
redirect_to '/players'
else
render 'new'
end
end
However I seem unable to access it properly
I have tried
perm = User.find(current_user).players_users.includes(:Permission)
if perm == "owner"
Which gives an ActiveRecord::AssociationNotFoundError, association named 'Permission' was not found on PlayersUser; perhaps you misspelled it?
I have also tried
perm = User.players_users.where(player_id = #player.id && user_id = current_user)
perm.permission
or
perm = User.Player.where(player_id = #player.id && user_id = current_user)
or
perm = User.players.where(player_id = #player.id && user_id = current_user)
Which gives an undefined method error.
undefined method `Player'
I know this is something small in my setup but cannot figure out what it is. Any help appreciated.
players_users and players are associated with User object, so you can fetch the results as,
current_user.players_users.pluck(:permission)
I've solved this issue before with my own code.
I'll post that in a second, but first you need to refactor your controller, the current is inefficient:
#app/controllers/players_controller.rb
class PlayersController < ApplicationController
def create
#player = current_user.players.new players_params
if #player.save
current_user.player_users.find(#player.id).update(permission: "owner")
redirect_to players_path
end
end
private
def player_params
params.require(:player).permit(....)
end
end
To access the permission of the player_user, you'll need to use the following:
#permission = current_user.player_users.find(#player.id).permission
--
A much more DRY approach (if you're explicitly setting permission as owner each time) would be to introduce an enum into the Player model to act as a default:
#app/models/player.rb
class Player < ActiveRecord::Base
enum permission: [:owner, :member, :moderator] #-> defaults to :owner
end
This will do away with having to define the permission in the create method (unless of course you want to change it):
#app/controllers/players_controller.rb
class PlayersController < ApplicationController
def create
#player = current_user.players.new players_params
redirect_to players_path if #player.save
end
end
To understand this, you must remember that since player_users is a join association, ActiveRecord will automatically populate it if you create a player on the current_user object (current_user.players).
Association Extensions
In regard to pulling the permission data, I built a script which appends the permission to the player object (uses proxy_association.target etc):
#current
#player = current_user.players.find params[:player_id]
#permission = current_user.player_users.find(params[:player_id]).permission
#script
#player = current_user.players.find params[:player_id]
#permission = #player.permission
It works similarly to an SQL Alias column - so whilst you cannot manipulate the data, it will allow you to call #user.players.find(params[:player_id].permission.. except it's all done in memory:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :player_users
has_many :players, through: :player_users, -> { extending PlayerPermission }
end
#app/models/concerns/player_permission.rb
module PlayerPermission
#Load
def load
permissions.each do |permission|
proxy_association.target << permission
end
end
#Private
private
#Permissions
def permissions
return_array = []
through_collection.each_with_index do |through,i|
associate = through.send(reflection_name)
associate.assign_attributes({permission: items[i]})
return_array.concat Array.new(1).fill( associate )
end
return_array
end
#######################
# Variables #
#######################
#Association
def reflection_name
proxy_association.source_reflection.name
end
#Foreign Key
def through_source_key
proxy_association.reflection.source_reflection.foreign_key
end
#Primary Key
def through_primary_key
proxy_association.reflection.through_reflection.active_record_primary_key
end
#Through Name
def through_name
proxy_association.reflection.through_reflection.name
end
#Through
def through_collection
proxy_association.owner.send through_name
end
#Captions
def items
through_collection.map(&:permission)
end
#Target
def target_collection
#load_target
proxy_association.target
end
end
--
As an aside, convention is to keep all aspects of a model name singular (ProductUser).

Restrict access to "destroy" feature in Ruby on Rails app

Thanks in advance for your help! I have a site built in Ruby on Rails (2.3.15, 1.8.7) that lets people create itineraries to different locations and with different activities at each location. The site has a way of ensuring that the current user can only delete the itineraries associated with his account. The code in itineraries_controller.rb to do this looks something like this:
def destroy
#itinerary = Itinerary.find(params[:id])
if #itinerary.user_id == current_user.id
#itinerary.destroy
respond_to do |format|
format.html { redirect_to('/home') }
format.xml { head :ok }
end
else
redirect_to '/'
end
end
This system works well. I want to do the same thing for the activities, however. In the activities_controller, I want to ensure that the current user can only delete the activities associated with his account.
Currently, here are my four models (with unnecessary stuff stripped out):
User.rb
class User < ActiveRecord::Base
has_many :itineraries
end
Itinerary.rb
class Itinerary < ActiveRecord::Base
has_many :locations
belongs_to :user
end
Location.rb
class Location < ActiveRecord::Base
belongs_to :itinerary
has_many :activities
end
Activity.rb
class Activity < ActiveRecord::Base
belongs_to :location
end
Here is where I'm starting with activities_controller.rb. FYI, my current_user.id is created by an authenticated_system.rb, so don't worry about that part.
def destroy
#activity = Activity.find(params[:id])
if #itinerary.user_id == current_user.id
#activity.destroy
respond_to do |format|
format.html { redirect_to("/") }
format.xml { head :ok }
end
else
redirect_to '/'
end
end
I tried to connect the models using a has_many :through approach, but I didn't know how to do it for more than three models, and I wasn't sure that was the right approach anyway. Thanks again for any help you can provide!
You can simply do
if #activity.location.itinerary.user_id == current_user.id
You really only need to ask the activity for its location, then ask the location for its itinerary, than ask it for its user. It's a bit of a violation of the Law of Demeter, but it will work.
loc = #activity.location
itin = loc.itinerary
if itin.user_id = current_user.id
...
That should work.

Resources