Rails 6: Only one profile per user should be created - ruby-on-rails

I'm currently working on a Rails 6 application. I have the following association. A User has a Profile and a Profile belongs to a User. When editing a profile for a user I ended up having two profiles for the user. I would like to have only one profile per user.
Edit form: profile/edit.html.erb
<%= form_for #profile do |f| %>
<div class="form-group">
<%= f.label :avatar %>
<%= f.file_field :avatar, as: :file, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :full_name, 'Full Name' %>
<%= f.text_field :full_name, autofocus: true, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :city, 'City' %>
<%= f.text_field :city, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :bio, 'Bio'%>
<p> Why did you join ArtsySpace?
What should other people here know about you?
</p>
<%= f.text_field :bio, class: "form-control"%>
</div>
<div class="form-group">
<%= f.submit "Edit profile", class: "btn btn-primary" %>
</div>
<% end %>
I see from the console that user 1 has 2 profiles. I'm not sure how a profile was created maybe I hit the create method from profile controller but mistake but I would like for this not to happen. Is there a validation for only one profile to belong to user?
class ProfilesController < ApplicationController
def new
#profile = current_user.build_profile
end
def create
#profile = current_user.create_profile(profile_params)
#profile.avatar.attach(params[:profile][:avatar])
if #profile.save
redirect_to #post
else
render 'new'
end
end
def show
#profile = Profile.find(params[:id])
end
def edit
#profile = current_user.profile
end
def update
#profile = current_user.profile
if #profile.update!(profile_params)
redirect_to #profile, notice: 'Profile was successfully updated.'
else
render :edit
end
end
def delete
#profile = current_user.profile.find(params[:id])
#profile.destroy
end
private
def profile_params
params.require(:profile).permit(:full_name, :city, :bio, :avatar)
end
end
I'm not sure if the issue comes from the way the routes are configured?
Rails.application.routes.draw do
devise_for :users
devise_scope :users do
resources :profiles, only: [:edit, :update]
end
resources :profiles, only: [:show]
resources :posts do
resource :comments, only: %i[show new create edit update]
end
end
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts
has_one :profile
accepts_nested_attributes_for :profile
end
From the below snippet you can see that a user has 2 profiles for user_id: 1
[#<Profile id: 3, user_id: 1, full_name: "steven ", city: "diego ", bio: "Because im ", created_at: "2019-06-12 23:11:49", updated_at: "2019-06-16 18:49:22">, #<Profile id: 4, user_id: 1, full_name: "andrew", city: "Tony", bio: "because i know ", created_at: "2019-06-12 23:12:35", updated_at: "2019-06-16 18:51:22">]
Not sure where the issue came from.

I once solved this by doing (I know it's strange, I'm just putting it out here as an idea)
has_one :profile
has_many :profiles
validates :profiles, length: { in: 0..1 }
https://guides.rubyonrails.org/active_record_validations.html#length

I decided to not to use a profile controller and just have one model User that can be used as the profile.

Related

Rails update form throwing "undefined method `[]' for nil:NilClass"

I'm trying to create an update form on Rails, for an object that has a foreignkey to another. However, it throws this error. I'm still very greenhorn with Ruby on Rails and have just been following a video tutorial, so I'm not quite sure how to interpret this. I am current using rails 5.0.0
In travelers_controllers.rb, below line
#prf = update_prof_params["profiles_attributes"]["0"]
throws the error
undefined method `[]' for nil:NilClass
edit.html.erb
<div class="col-md-7 col-md-offset-3 main">
<% provide(:title, "Edit user")%>
<center><h1>Update your profile</h1></center>
<%= form_for(#person) do |f| %>
<%= render 'shared/error_messages' %>
<div class="col-md-12">
<%= render 'layouts/profilefields', f: f %>
<%= f.submit "Save Changes", class: "btn btn-large btn-primary" %>
</div>
<% end %>
</div>
_profilefields.html.erb
<%= f.fields_for :profiles do |prf|%>
<!--
<% if !#profileInfo["avatar"].blank? %>
<%= image_tag #contactInfo.avatar_url(:medium).to_s, :class=>"profilePhoto" %>
<% end %>
<div class="photoPreview">
<i class="fa fa-upload photoUpload"></i>
<p id="uploadClick">Click to Upload</p>
</div>
<%= prf.file_field :avatar, accept: 'image/png,image/gif,image/jpeg, image/jpg', id: 'uploadAvatar' %>
<p class="deletePhoto">Delete</p>
-->
<%= prf.label :about %>
<%= prf.text_field :about, :class => "form-control" %>
<%= prf.label :why %>
<%= prf.text_field :why, :class => "form-control" %>
<%= prf.label :goals %>
<%= prf.text_field :goals, :class => "form-control" %>
<%= prf.hidden_field :traveler_id, value: current_traveler.id %>
<% end %>
travelers_controller.rb
class TravelersController < ApplicationController
def edit
#person = Traveler.find(params[:id])
#profileInfo = Profile.find_or_initialize_by(traveler_id: params[:id])
##profileInfo[:email] = current_traveler.email
#This builds the form
#person.build_profile(#profileInfo.attributes)
end
def show
end
def update
#prf = update_prof_params["profiles_attributes"]["0"]
#prof = Profile.find_or_create_by(traveler_id: current_traveler.id)
if #prof.update_attributes(prf)
flash[:success] = "Profile updated"
redirect_to feed_path
else # Failed. Re-render the page as unsucessful
render :edit
end
end
private
def update_prof_params
params.require(:traveler).permit(profiles_attributes: [:about, :why, :goals,
:traveler_id])
end
end
and the models
class Profile < ApplicationRecord
belongs_to :traveler, foreign_key: "traveler_id"
end
class Traveler < ApplicationRecord
# Include default devise modules. Others available are:
# , :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
has_one :profile
end
In TravelersController, the method update should be used for update traveler, not profile, so you can use mass-update via nested attribute like this:
def update
#traveler = Traveler.find(params[:id])
if #traveler.update(update_prof_params)
flash[:success] = "Profile updated"
redirect_to feed_path
else # Failed. Re-render the page as unsucessful
render :edit
end
end
So the above allow you to create/update profile which belongs to traveler. Besides, ensure the nested attribute was defined in your model:
traveler.rb
class Traveler < ActiveRecord::Base
# Your code here
#....
# Make sure define this
accepts_nested_attributes_for :profile
end
Update: The permitted params should be:
def update_prof_params
params.require(:traveler).permit(profile_attributes: [:about, :why, :goals, :traveler_id])
end
As you see profile_attributes should be used instead of profiles_attributes because traveler has one profile only

Rails - nested model input data not appearing in "show" page

I am having trouble figuring out how to make some data collected through a nested model appear on the "show" page. I have a rails app with 3 models, a User model, a Project model, and a Team model. The model associations are as follows:
Project:-
class Project < ActiveRecord::Base
has_many :users, :through => :team
has_one :team, :dependent => :destroy
accepts_nested_attributes_for :team, allow_destroy: true
end
Team:-
class Team < ActiveRecord::Base
belongs_to :project
has_many :users
end
User:-
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_and_belongs_to_many :teams
end
Every project has one team, and every team consists of many users who are already saved in the database. What I would like to do exactly is to make it possible to select multiple existing users within the project form (through a nested form) and save them to a model called team. I managed to get the form working correctly, but im not sure how to go about saving the data collected to the team model, and then to make the group of users that were selected (the team) to appear in project's show page. Please help!
P.S I used the nested form gem to add multiple team members within the project's form.
Projects Show page:-
<%= bootstrap_nested_form_for(#project, :html => {:multipart => true}, layout: :horizontal) do |f| %>
<% f.fields_for :teams do |builder| %>
<% if builder.object.new_record? %>
<%= builder.collection_select :user, User.all, :id, :email, { prompt: "Please select", :selected => params[:user], label: "Employee" } %>
<% else %>
<%= builder.hidden_field :_destroy %>
<%= builder.link_to_remove "Remove" %>
<% end %>
<%= f.link_to_add "Add Team Member", :teams %>
<%= f.submit %>
<% end %>
projects controller:-
class ProjectsController < ApplicationController
before_action :set_project, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#projects = Project.all
respond_with(#projects)
end
def show
respond_with(#project)
end
def new
#project = Project.new
#project.pictures.build
#project.teams.build
respond_with(#project)
end
def edit
#project = Project.find(params[:id])
#project.pictures.build
#project.teams.build
end
def create
#project = Project.new(project_params)
if #project.save
flash[:notice] = "Successfully created project."
redirect_to #project
else
render :action => 'new'
end
end
def update
#project.update(project_params)
respond_with(#project)
end
def destroy
#project.destroy
respond_with(#project)
end
private
def set_project
#project = Project.find(params[:id])
end
def project_params
params.require(:project).permit(:id, :title, :description, :status, :phase, :location, :image, pictures_attributes: [:id, :image], teams_attributes: [:project_id, :user_id])
end
end

Rails - Nested form not showing on "show" page

I am having trouble figuring out how to make some data collected through a nested model appear on the "show" page. I have a rails app with 3 models, a User model, a Project model, and a Team model. The model associations are as follows:
Project:-
class Project < ActiveRecord::Base
has_many :users, :through => :team
has_one :team, :dependent => :destroy
accepts_nested_attributes_for :team, allow_destroy: true
end
Team:-
class Team < ActiveRecord::Base
belongs_to :project
has_many :users
end
User:-
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_and_belongs_to_many :teams
end
Every project has one team, and every team consists of many users who are already saved in the database. What I would like to do exactly is to make it possible to select multiple existing users within the Project form (through a nested form) and save them to a model called Team. I managed to get the form working correctly, but im not sure how to go about saving the data collected to the team model, and then to make the group of users that were selected (the team) to appear in project's show page, as there are 3 models involved. The Please help!
P.S I used the nested form gem to add multiple team members within the project's form.
Projects Show page:-
<%= bootstrap_nested_form_for(#project, :html => {:multipart => true}, layout: :horizontal) do |f| %>
<% f.fields_for :teams do |builder| %>
<% if builder.object.new_record? %>
<%= builder.collection_select :user, User.all, :id, :email, { prompt: "Please select", :selected => params[:user], label: "Employee" } %>
<% else %>
<%= builder.hidden_field :_destroy %>
<%= builder.link_to_remove "Remove" %>
<% end %>
<%= f.link_to_add "Add Team Member", :teams %>
<%= f.submit %>
<% end %>
projects controller:-
class ProjectsController < ApplicationController
before_action :set_project, only: [:show, :edit, :update, :destroy]
respond_to :html
def index
#projects = Project.all
respond_with(#projects)
end
def show
respond_with(#project)
end
def new
#project = Project.new
#project.pictures.build
#project.teams.build
respond_with(#project)
end
def edit
#project = Project.find(params[:id])
#project.pictures.build
#project.teams.build
end
def create
#project = Project.new(project_params)
if #project.save
flash[:notice] = "Successfully created project."
redirect_to #project
else
render :action => 'new'
end
end
def update
#project.update(project_params)
respond_with(#project)
end
def destroy
#project.destroy
respond_with(#project)
end
private
def set_project
#project = Project.find(params[:id])
end
def project_params
params.require(:project).permit(:id, :title, :description, :status, :phase, :location, :image, pictures_attributes: [:id, :image], teams_attributes: [:project_id, :user_id])
end
end

Rails + Devise Nested Registration form undefined method 'build_address'

Hi this question is basically the same as this one, which had no responses. I'm trying to combine the Devise registration form to include fields that produce not only a "user", but a "customer" object, an "account" object for that customer, and an "address" for that customer.
When visitor clicks "Sign Up", the registration form should include the standard Devise stuff, but also the fields for the creation of the other objects. Instead, I get this error:
NoMethodError in Registrations#new
undefined method `build_address' for #
Extracted source (around line #6):
<div class="panel panel-default" style="width: 14em;">
<% resource.build_customer if resource.customer.nil? %>
<% resource.build_account if resource.accounts.nil? %>
<% resource.build_address if resource.address.nil? %>
<%= form_for(resource, as: resource_name, url: >registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<h3>User Info</h3>
Rather than explaining all the relationships, here are the models:
user.rb
class User < ActiveRecord::Base
before_create :generate_id
# Virtual attribute for authenticating by either username or email
# This is in addition to a real persisted field like 'username'
attr_accessor :login
has_one :administrator
has_one :customer
has_many :accounts, through: :customer
accepts_nested_attributes_for :customer, :allow_destroy => true
accepts_nested_attributes_for :accounts, :allow_destroy => true
has_one :address, through: :customer
accepts_nested_attributes_for :customer, :allow_destroy => true
accepts_nested_attributes_for :address, :allow_destroy => true
validates_uniqueness_of :email, :case_sensitive => false
validates_uniqueness_of :id
validates :username,
:presence => true,
:uniqueness=> {
:case_sensitive => false
}
# User ID is a generated uuid
include ActiveUUID::UUID
natural_key :user_id, :remember_created_at
belongs_to :user
# specify custom UUID namespace for the natural key
uuid_namespace "1dd74dd0-d116-11e0-99c7-5ac5d975667e"
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :timeoutable, :recoverable, :trackable, :validatable
# Generate a random uuid for new user id creation
def generate_id
self.id = SecureRandom.uuid
end
# Allow signin by either email or username ("lower" function might have to be removed?)
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
where(conditions.to_h).where(["lower(username) = :value OR lower(email) = :value", { :value => login.downcase }]).first
else
where(conditions.to_h).first
end
end
end
customer.rb
class Customer < ActiveRecord::Base
belongs_to :user
has_one :address
has_many :accounts
validates :phone1, :firstname, :lastname, presence: true
end
account.rb
class Account < ActiveRecord::Base
belongs_to :customer
belongs_to :user
has_one :acct_type
has_many :acct_transactions
validates :acct_type, presence: true
end
address.rb
class Address < ActiveRecord::Base
belongs_to :customer
belongs_to :user
validates :zip_code, presence: true
validates :address1, presence: true
has_one :zip_code
has_one :state, through: :zip_code
end
The two controllers in question:
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
# GET /users/sign_up
def new
#user = current_user
#customer = nil ##user.customer
#account = nil ##customer.account
#address = nil ##customer.address
# Override Devise default behavior and create a customer, account, and address as well
build_resource({})
resource.build_customer
respond_with self.resource
build_resource({})
resource.build_account
respond_with self.resource
build_resource({})
resource.build_address
respond_with self.resource
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u|
.permit(:username, :email, :password, :password_confirmation,
:customer_attributes => [:phone1, :phone2, :title, :firstname, :lastname],
:account_attributes => :acct_type,
:address_attributes => [:address1, :address2, :zip_code])
}
end
end
addresses_controller.rb (The important parts)
def new
#customer = current_user.customer
#address = #customer.address.build(:customer_id => #customer.id,
:address1 => nil,
:address2 => nil,
:zip_code => nil)
end
def create
#customer = current_user.customer
#address = #customer.address.build(:customer_id => #customer.id,
:address1 => nil,
:address2 => nil,
:zip_code => nil)
respond_to do |format|
if #address.save
format.html { redirect_to #address, notice: 'Address was successfully created.' }
format.json { render :show, status: :created, location: #address }
else
format.html { render :new }
format.json { render json: #address.errors, status: :unprocessable_entity }
end
end
end
And here is the view where the exception is raised (It's really long so actually the important parts):
<h1>Create an account</h1>
<div class="form-group">
<div class="panel panel-default" style="width: 14em;">
<% resource.build_customer if resource.customer.nil? %>
<% resource.build_account if resource.accounts.nil? %>
<% resource.build_address if resource.address.nil? %>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<h3>User Info</h3>
<div class="form-group">
<!-- fields for User object -->
<div class="field">
<%= f.label :username %><br />
<%= f.text_field :username, autofocus: true %>
</div>
...
<!-- fields for Customer object -->
<%= f.fields_for :customer do |customer_fields| %>
<div class="field">
<%= customer_fields.label :firstname %>
<%= customer_fields.text_field :firstname %>
</div>
...
<!-- fields for Account object -->
<%= f.fields_for :account do |account_fields| %>
<div class="field">
<%= account_fields.label :acct_type %>
<%= account_fields.text_field :acct_type %>
</div>
<% end %>
<!-- fields for Address object -->
<%= f.fields_for :address do |address_fields| %>
<div class="field">
<%= address_fields.label :address1 %>
<%= address_fields.text_field :address1 %>
</div>
...
The exception is pointing to the block of statements at the top...
<% resource.build_customer if resource.customer.nil? %>
<% resource.build_account if resource.accounts.nil? %>
<% resource.build_address if resource.address.nil? %>
... which has given me trouble before. Before the current error I was getting a similar error from the second line ("build_account"). But that turned out to be a pluralization issue, which I believe I've fixed. Since the HTML is read sequentially, it would seem that there is no problem with the first two build_ methods. Why is there then a problem with the build_address method?
I need to fix this error before I can know if the whole thing will actually work or not. Any ideas?
Thanks
It's Rails 4.1.8 / Devise 3.4.1
The trouble turned out to be the syntax I was using create multiple resource objects. It would pass one, but ignored the rest. What I ended up doing to make it work (or at least make the error go away) was to override the build_resource method to accept an array of parameters for each object to be instantiated:
def new
#user = current_user
build_resource({})
self.resource[:customer => Customer.new, :account => Account.new, :address => Address.new]
respond_with self.resource
end
def build_resource(hash=nil)
hash ||= params[resource_name] || {}
self.resource = resource_class.new(hash)
end
def create
# Override Devise default behavior and create a customer, account, and address as well
resource = build_resource(params[:user])
if(resource.save)
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_up_path_for(resource)
else
render :action => "new"
end
end
Also, I removed the three lines at the top of the form view as they attempted to do some sort of pre-validation in the view and just caused problems. Plenty of validation will happen when the form is submitted. This seems to be doing something good. Now I'm working with the form view and having trouble getting each part to render. Fields_for is rendering fields for User and Account models, but not Customer or Address.

Profile Model with Devise Users in Rails

I'm trying to create a separate Profile model for Devise Users with things such as location, biography, blah, blah, blah. The problem is that I can't get it to save to the database.
My users are called "artists".
### /routes.rb ###
get 'artists/:id/new_profile' => 'artists/profiles#new', as: :profile
post 'artists/:id/new_profile' => 'artists/profiles#create'
### artists/profiles_controller.rb ###
class Artists::ProfilesController < ApplicationController
before_action :authenticate_artist!
def new
#artist = current_artist
#profile = ArtistProfile.new
end
def create
#artist = current_artist
#profile = ArtistProfile.new(profile_params)
if #profile.save
redirect_to current_artist
else
render 'new'
end
end
end
### /artist.rb ###
class Artist < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable, :timeoutable
has_one :artist_profile, dependent: :destroy
### /artist_profile.rb ###
class ArtistProfile < ActiveRecord::Base
belongs_to :artist
validates :artist_id, presence: true
end
### /views/artists/profiles/new.html.erb ###
<%= form_for(#profile, url: profile_path) do |f| %>
<div class="field">
<%= f.label :biography, "biography", class: "label" %>
<%= f.text_area :biography, autofocus: true , class: "text-field" %>
</div>
<div class="field">
<%= f.label :location, "location", class: "label" %>
<%= f.text_field :location, class: "text-field" %>
</div>
...
...
...
<div class="actions">
<%= f.submit "create profile", class: "submit-button" %>
</div>
<% end %>
What am I doing wrong here?
You need to initialise the profile using the current_artist object.
class Artists::ProfilesController < ApplicationController
before_action :authenticate_artist!
def new
#artist = current_artist
#profile = #artist.build_profile
end
def create
#artist = current_artist
#profile = #artist.build_profile(profile_params)
if #profile.save
redirect_to current_artist
else
render 'new'
end
end
end
Update:
To use this example your association should be like
class Artist < ActiveRecord::Base
has_one :profile, class_name: ArtistProfile
end
Make sure you set the artist_id for the profile before attempting to save.
#profile = ArtistProfile.new(profile_params, artist_id: #artist.id)
or
#profile = ArtistProfile.new(profile_params)
#profile.artist_id = #artist.id
should work.
In your controller you are missing the profile_params method.
private
def profile_params
params.require(:profile).permit(:biography, :location)
end

Resources