In my user model, I have
belongs_to :admin_creator, foreign_key: :created_by_user_id, class_name: "User"
and in my create controller action I have...
def create
#user = User.new(user_create_params)
#user.created_by_user_id = current_user.id
#user.status_code = 'P' #set status code to pending
begin
#user.save!
render json: "User #{#user.email} added", status: :created
rescue StandardError => e
render json: #user.errors.full_messages, status: :unprocessable_entity
end
end
How can I recast my code to to use model association for "current_user" who would be the admin_creator?
When create action is called, current_user is the current admin user who is adding another (child) user account. I'm thinking it would be along the lines of... #user = current_user.users.build(user_create_params) or similar
Add a has_many to the User model like this:
has_many :admin_children, foreign_key: :created_by_user_id, class_name: "User"
Then you can do:
current_user.admin_children.create(user_create_params)
Related
I have the following M2M through associations for these 3 models
Customer -> Residences <- Properties
Also Property model is related to Address:
class Address < ApplicationRecord
has_one :property
end
A customer will always exist before creating a Property.
A property is created by submitting an Address.
Here is the controller action, which works except on success the render always returns 2 properties (ie. basically 2 residence records).
However, only one is in the database. I understand it is related to stale objects, but cannot figure out how to solve it.
I tried adding #customer.reload and #customer.reload.residences and #customer.reload.properties but still get 2 records.
# POST /customers/:id/properties
def create
#customer = set_customer
Customer.transaction do
address = Address.find_by_place_id(address_params[:place_id])
if address.nil?
#property = #customer.properties.create
#property.address = Address.new(address_params)
if #property.save
#customer.reload
render json: #customer, status: :created
else
render json: #property.errors, status: :unprocessable_entity
end
else
# irrelevant code to the problem
end
end
end
def set_customer
Customer.find(params[:customer_id])
end
A comment on this question (from #Swaps) indicates using << instead of create can sometimes result in duplicates, but whichever way I do it I always get 2.
EDIT
I managed to force it work like this but this feels like a hack:
if #property.save
#customer = set_customer
render json: #customer, status: :created
else
** UPDATE - the models **
class Customer < ApplicationRecord
has_many :residences
has_many :properties, through: :residences
end
class Residence < ApplicationRecord
belongs_to :customer
belongs_to :property
end
class Property < ApplicationRecord
belongs_to :address
has_many :residences
has_many :customers, through: :residences
end
class Address < ApplicationRecord
has_one :property
has_one :location # ignore this, not relevant
end
You're trying to do manually what ActiveRecord can do automatically with accepts_nested_attributes_for. It even works with has_many through operations.
class Customer < ApplicationRecord
has_many: :residences, inverse_of :customer
has_many: :properties, through: :residences
accepts_nested_attributes_for :residences
end
class Residence < ApplicationRecord
belongs_to :customer
belongs_to :property
validates_presence_of :customer
validates_presence_of :property
accepts_nested_attributes_for :property
end
class Property < ApplicationRecord
has_one :address
has_many :residences
has_many :customers, through: :residences
accepts_nested_attributes_for :address
end
class Address < ApplicationRecord
belongs_to :property
end
class CustomersController < ApplicationController
def create
customer = Customer.new(customer_params)
if customer.save
redirect_to customer, notice: 'Customer saved!'
else
render :new
end
end
def customer_params
params.require(:customer).permit(
name:, ...,
residences_attributes: [
property_attributes: [
name, ...,
address_attributes: [
street, city, state, postal_code, ...
]
]
]
)
end
end
References:
https://www.pluralsight.com/guides/ruby-on-rails-nested-attributes
https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through
Could you please try this?
def create
#customer = set_customer
Customer.transaction do
address = Address.find_by_place_id(address_params[:place_id])
if address.nil?
#customer.properties.new(address_params)
if #customer.save
render json: #customer, status: :created
else
render json: #customer.errors, status: :unprocessable_entity
end
else
# irrelevant code to the problem
end
end
end
I was thinking do you really need #property instance variable. Is it for your view files?
Update 1
Could you please add your Customer and Residence model as like this:
Customer model
class Customer < ApplicationRecord
has_many :residences
has_many :properties, through: :residences
end
Residence model
class Residence < ApplicationRecord
belongs_to :customer
belongs_to :property
end
The problem was stale objects in ActiveRecord versus what is in the database after saving.
The ".reload" did not work, I have to actually force ActiveRecord using my hack to force ActiveRecord to find the customer in the database again, and that forces a reload (I presume it invalidates the AR cache):
def create
#customer = set_customer
Customer.transaction do
address = Address.find_by_place_id(address_params[:place_id])
if address.nil?
#property = #customer.properties.create
#property.address = Address.new(address_params)
if #property.save!
#customer = set_customer # force reload from db
render json: #customer, status: :created
end
else
address.update!(address_params)
if #customer.properties.find_by_id(address.property.id).nil?
# although we updated the address, that is just a side effect of this action
# the intention is to create an actual residence record for this customer
#customer.properties << address.property
#customer = set_customer # force reload from db
render json: #customer, status: :created
else
#customer.errors.add(:customer, 'already has that property address')
render json: ErrorSerializer.serialize(#customer.errors), status: :unprocessable_entity
end
end
end
end
def set_customer
Customer.find(params[:customer_id])
end
I have two tables Role and User, and I linked those two tables with has_and_belongs_to_many relationship with rails.
I'm successfully insert the data into the third table which is created by has_and_belongs_to_many relationship. Using the following code
def create
user_params[:password] = User.hash(user_params[:password])
#user = User.new(:first_name => user_params[:first_name],
:last_name=>user_params[:last_name],
:email => user_params[:email],
:contact_number=>user_params[:contact_number],
:password=>user_params[:password])
#roles = user_params[:roles];
for role in #roles
#user.roles << Role.find(role)
end
if #user.save
respond_to do |format|
msg = { :status => "ok", :message => "User Creation Success!" }
format.json { render :json => msg }
end
end
end
Now my problem is how do I read the values from the relationship table and how do I update any value to the relationship table.
Lets suppose you setup your models User Roles like so:
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
Abd added users_roles_tabletable
You can then retrieve associated data like normal saying
User.first.roles
and
Role.first.users
I have a Match model with 2 players fields that have a belongs_to association with the User model
Model
class Match < ApplicationRecord
belongs_to :player1, :class_name => 'User', :foreign_key => 'player1'
belongs_to :player2, :class_name => 'User', :foreign_key => 'player2'
end
When creating a Match via the API (using a Postman POST request) I tried passing the user_id of the players but got a TypeMismatch error indicating the controller expected a User object but got a Fixnum.
Looking at this line:
#match = Match.new(match_params)
the error makes sense, so I modified my default scaffold generated controllers to look like this instead:
def create
#match = Match.new
#match.player1 = User.find(params[:match][:player1])
#match.player2 = User.find(params[:match][:player2])
if #match.save
render json: #match, status: :created, location: #match
else
render json: #match.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /matches/1
def update
if #match.update( :player1 => User.find(params[:match][:player1]),
:player2 => User.find(params[:match][:player2])
)
render json: #match
else
render json: #match.errors, status: :unprocessable_entity
end
end
It works, but the solution seems "inelegant".
Is there a better way to pass values to a controller with a belongs_to association?
Could you please try changing your foreign_key in Match model from player1, player2 to player1_id, player2_id respectively, via database migrations. Because your foreign_key and belongs_to associations are same? Please let me know whether it works!
I'm creating an app where users can create embeddable forms that collect contacts for them (email subscribers).
When the user gets a new email subscriber, a new contact is created with the email address, and the contact is associated with the user and the form it was create through.
However right now if the same email gets entered twice, it creates a new contact, instead of updating an existing contact associated with the user.
In my Contacts controller, I'm trying to use the first_or_create function to check if the user already has a contact with that email address, before creating a new one.
def create
#user = #form.user
#contact = Contact.where(:user_id => #user, :email => :email).first_or_create(contact_params)
#contact.user = current_user
respond_to do |format|
if #contact.save
format.html { redirect_to :back, notice: 'New contact created!' }
format.json { render :show, status: :created, location: #contacts }
else
format.html { render :new }
format.json { render json: #contact.errors, status: :unprocessable_entity }
end
end
end
While this doesn't return an error when creating a contact, it still creates a duplicate contact.
How do I properly do this?
NOTES: As you can see, I'm also trying to pass contact_params, which includes important information like the contacts name, etc.
Here's the models for reference...
class User < ActiveRecord::Base
has_many :forms, :dependent => :destroy
has_many :contacts, :dependent => :destroy
end
class Contact < ActiveRecord::Base
belongs_to :user
belongs_to :contactable, polymorphic: true
end
class Form < ActiveRecord::Base
belongs_to :user
has_many :contacts, as: :contactable
end
Thanks for any help.
When creating your contacts table you should pass the unique: true parameter. So it should be ...t.string :email, unique: true...instead of just t.string :email
In your contact model, put
validates :email, uniqueness: { scope: :user_id }
If you try to create another contact with the same email, validations will fail and a new contact won't be created.
I just created a create feature for adding a team together with adding the members to a said team.
The form contains the following:
Name of the team.
Team's department.
Leader (value is the user id, but displayed as full name)
Members (values are user ids, but displayed also as full names) //I used a special select menu called select2.
Here are the models. I'll only show the associations and some methods related to my problem.
user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :teams, through: :memberships
accepts_nested_attributes_for :memberships, :teams
end
team.rb
class Team < ActiveRecord::Base
has_many :memberships
has_many :teams, through: :memberships
accepts_nested_attributes_for :memberships, :users
def build_membership(user_ids)
unless user_ids.blank?
user_ids.each do |id|
self.users << User.find_by_id(id)
end
end
end
end
membership.rb
class Membership < ActiveRecord::Base
belongs_to :team
belongs_to :user
#Note that leader and members are both users
end
Here is the controller, with the create and update methods.
class TeamsController < ApplicationController
def create
#team = Team.new(team_params)
#team.build_membership(build_members_array(members_params))
if #team.save
flash.now[:success] = 'Team was successfully created.'
redirect_to #team
else
flash.now[:notice] = #team.errors.full_messages
render "new"
end
end
def update
# TO-DO: update leader and members (i.e. add or remove member)
if #team.update(team_params)
flash.now[:success] = "Team was successfully updated."
redirect_to #team
else
flash.now[:notice] = #team.errors.full_messages
render "edit"
end
end
private
def team_params
params.require(:team).permit(:name,:department)
end
def members_params
params.require(:team).permit(:leader, members:[])
end
def build_user_ids_array(members)
#put ids of leader and members in an array
end
end
It seems that only the name and department attributes are only updated while the leader and the members are not. Should I create my own method again for updating the roster of the team or do something else in mind?
#team.build_membership would be if you are passing in the params to create a new user, but you are using existing User's so this wouldn't apply. You will want to instead fetch all of the User's by their id and then add that the the Team. You also need to permit the id in the members_attributes in your member_params method.
Something like:
def create
#team = Team.new(team_params)
#team.memberships << users_from_params
if #team.save
flash.now[:success] = 'Team was successfully created.'
redirect_to #team
else
flash.now[:notice] = #team.errors.full_messages
render "new"
end
end
def members_params
params.require(:team).permit(:leader, members_attributes: [:id])
end
def users_from_params
#This members_params[:members_attributes].values should be an array of `id`s
User.find(members_params[:members_attributes].values)
end