nested forms : update child foreign key - ruby-on-rails

I have a nested association:
class User < ActiveRecord::Base
has_many :hostels
accepts_nested_attributes_for :hostels
end
class Hostel < ActiveRecord::Base
belongs_to :user
end
The form :
<%= form_for #user do |f| %>
<%= f.label :email %><br>
<%= f.text_field :email %>
<% f.object.hostels << #hostel -%>
<%= f.fields_for :hostels do |ff| %>
<%= ff.hidden_field :id %>
<% end -%>
<%= f.submit %>
<% end -%>
the controller
def create
#user = User.new(user_params)
raise #user.hostels.inspect
end
private
def user_params
params.require(:user).permit(:email, hostels_attributes: [:id])
end
I would like to relink existing records of hostels to new users by updating hostel foreign key. This way, it definitly don't work.
Tried update_only: true parameter to nested too.
Any ideas about the subject or am I totally wrong about trying to do the operation like that ?

you can use a multiple select for hotels in you form, then in the controller you must require hostel_ids.
Models:
class User < ActiveRecord::Base
has_many :hostels
end
class Hostel < ActiveRecord::Base
belongs_to :user
end
Form: where :name is what you see in your multiple select
<%= form_for #user do |f| %>
<%= f.label :email %><br>
<%= f.text_field :email %>
<%= f.label "Hostels" %><br>
<%= select_tag :hotel_ids, options_for_select(Hostel.all.map{|h| [h.name, h.id]}), { :multiple => true } %>
<%= f.submit %>
<% end %>
Controller:
def create
#user = User.new(user_params)
end
private
def user_params
params.require(:user).permit(:email, hostel_ids)
end
I did not test the code but must work well.

Related

Create multiple new records from checkboxes in form using nested attributes in Rails

I'm trying to come up with a contact form that creates a contact record and potentially multiple location records, if multiple locations are checked in a list of checkboxes. I thought of having all location records created and then destroyed, if they aren't checked. I don't think that's optimal though.
I'm using many to many relationships in the models.
This is what they look like at the moment:
contact.rb
class Contact < ApplicationRecord
has_many :contact_locations, dependent: :destroy
has_many :locations, through: :contact_locations
accepts_nested_attributes_for :contact_locations, allow_destroy: true, reject_if: :empty_location?
private
def empty_location?(att)
att['location_id'].blank?
end
end
location.rb
class Location < ApplicationRecord
has_many :locations, dependent: :destroy
has_many :contacts, :through => :contact_locations
has_many :contact_locations
end
contact_location.rb
class ContactLocation < ApplicationRecord
belongs_to :location
belongs_to :contact
end
contacts_controller.rb
def new
#contact = Contact.new
#locations = Location.all
4.times {#contact.contact_locations.new}
end
private
def contact_params
params.require(:contact).permit(:name, :phone, ..., contact_locations_attributes: [:location_ids])
end
new.html.rb
<%= form_with model: #contact do |f| %>
...
<%= #locations.each do |location| %>
<%= f.fields_for :contact_locations do |l| %>
<%= l.check_box :location_id, {}, location.id, nil %><%= l.label location.name %>
<% end %>
<% end %>
...
<% end %>
Does anyone how to make it work properly?
I'm working on Ruby 2.5.1 and Rails 5.2.1.
Thanks a lot.
I think your solution is the form objects pattern.
You can have something like this:
<%= form_for #user do |f| %>
<%= f.email_field :email %>
<%= f.fields_for #user.build_location do |g| %>
<%= g.text_field :country %>
<% end %>
<% end%>
And convert it in something more readable that permits you to instance the locations inside the registration object, checking the value of the checkboxes.
<%= form_for #registration do |f| %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.input :password %>
<%= f.text_field :password %>
<%= f.input :country %>
<%= f.text_field :country %>
<%= f.input :city %>
<%= f.text_field :city %>
<%= f.button :submit, 'Create account' %>
<% end %>
Here you will find how to apply the pattern: https://revs.runtime-revolution.com/saving-multiple-models-with-form-objects-and-transactions-2c26f37f7b9a
I ended up making it work with Kirti's suggestion on the following question:
Rails Nested attributes with check_box loop
It turns out I needed to make a small adjustment in my form's fields_for tag.
Thanks a lot the help!

Add User to project using his email

I have projects which can have one projectOwner and multiple projectMembers. After creating my project, I want to be able to add users to this project.
Right now my problem is that I have to enter the user_id and project_id when creating a new projectMember. The form is expecting a user_id and project_id so how can I change that so that it takes an email and then finds the user_id related to it.
View:
<%= form_for([#project, #project.project_members.build]) do |f| %>
<p>
<%= f.label :project_id %><br>
<%= f.text_field :project_id %>
</p>
<p>
<%= f.label :user_id %><br>
<%= f.text_field :user_id %>
</p>
<p>
<%= f.submit :Add, class: 'btn btn-info' %>
</p>
<% end %>
Controller:
def create
#project_member = ProjectMember.new(member_params)
#project_member.save
end
private
def member_params
params.require(:project_member).permit(:user_id, :project_id)
end
Model relation:
class User < ApplicationRecord
has_many :roles
has_many :projects, through: :roles
end
class Role < ApplicationRecord
belongs_to :user
belongs_to :project
end
class ProjectOwner < Role
end
class ProjectMember < Role
end
class Project < ApplicationRecord
has_many :project_members
has_many :members, class_name: "User", through: :project_members
has_one :project_owner
has_many :owners, class_name: "User", through: :project_owner
end
Right now my routes look like this but I feel like this isnt the right way of doing it:
devise_for :users
resources :users do
resources :projects
end
resources :projects do
resources :project_members
end
The form is expecting a user_id and project_id so how can I change
that so that it takes an email and then finds the user_id related to
it.
You can have an input for the email in the form and allow the controller to do the rest
<%= form_for([#project, #project.project_members.build]) do |f| %>
<p>
<%= f.label :project_id %><br>
<%= f.text_field :project_id %>
</p>
<p>
<%= label_tag :email %><br>
<%= email_field_tag :email %>
</p>
<p>
<%= f.submit :Add, class: 'btn btn-info' %>
</p>
<% end %>
In the controller, find the user with the email
def create
#project_member = ProjectMember.new(member_params)
#user = User.find_by(email: params[:email])
#project_member.user_id = #user.id
#project_member.save
end
Also, you don't need to provide project_id explicitly in the form. The #project in the form will give you the project_id. You just need to let the controller do the mapping
<%= form_for([#project, #project.project_members.build]) do |f| %>
<p>
<%= label_tag :email %><br>
<%= email_field_tag :email %>
</p>
<p>
<%= f.submit :Add, class: 'btn btn-info' %>
</p>
<% end %>
In the controller,
def create
#project = Project.find(params[:project_id])
#project_member = #project.project_members.new(member_params)
#user = User.find_by(email: params[:email])
#project_member.user_id = #user.id
#project_member.save
end
In your view, replace user_id with user_email field:
<p>
<%= f.label :user_email %><br>
<%= f.text_field :user_email %>
</p>
In controller, get the email
def create
user = User.find_by_email(params[:project_member][:user_email]) # find the user by email
#project_member = ProjectMember.new(:project_id => params[:project_member][:project_id]) # create new project member with project id
#project_member.users << user # assign user to newly created project member
#project_member.save
end
private
def member_params
params.require(:project_member).permit(:user_email, :project_id)
end
Hope it helps!
Note: Please make sure if the User we find by email exists!
One way you achieve this is modifying your member_params as
def member_params
member_params = params.require(:project_member).permit(:email, :project_id)
if (user_id = User.find_by_email(member_params[:email]).try(:id)).present?
member_params.merge!(user_id: user_id)
member_params.permit!
else
# display error
end
end
Or let your params be as it is
def member_params
params.require(:project_member).permit(:email, :project_id)
end
in your controller,
def create
#project_member = ProjectMember.build_by(member_params)
if #project_member.save
# success
else
# failure
end
end
and model,
def self.build_by(params)
project_member = self.new(params)
project_member.user_id = User.find_by_email(params[:email]).try(:id)
end
I think, second option is more cleaner and logic moves to model..

how to add details in two database table in one submit

I have 3 tables: coaches, categories and also a join table categories_coaches, on submit I want to store category_id and coach_id in join table categories_coaches and name, email, university, batch, phone in coach table. how to do so?
now details are storing in coach table but not storing in join table
please help me to solve this problem.
coach.rb
class Coach < ActiveRecord::Base
has_and_belongs_to_many :categories
end
category.rb
class Category < ActiveRecord::Base
belongs_to :coach
end
registrationcontroller.erb
class Coaches::RegistrationsController < Devise::RegistrationsController
def new
#individual=#individual ||= Coach.new
super
end
def create
build_resource sign_up_params
#individual=#individual ||= Coach.new
super
end
private
def sign_up_params
params.require(:coach).permit(:name, :email, :university, :batch, :linkedin_url, :code, :phone,category_ids: []
)
end
end
view page
<%= simple_form_for(#individual, as: :coach, url: registration_path(:coach)) do |f| %>
<%= f.input :name, required: true, %>
<%= f.input :university %>
<%= f.input :batch %>
<%= f.input :email%>
<%= f.input :phone%>
<div class="category-scroll">
<% Category.all.each do |c| %>
<% if c.parent_id != nil %>
<div class="category-left">
<%= check_box_tag "category_ids[]", c.id, false, :id => "category_ids_#{c.id}" %>
<%= c.name %>
</div>
<% else %>
<b><%= c.name %></b>
<% end %>
<% end %>
</div>
<div class="form-group">
<%= f.button :submit, "SUBMIT", class: "apply-continue_form" %
<% end %>
What you've mentioned sounds like a has_and_belongs_to_many relationship to me.
I'll detail what you should do, and the underlying mechanics of the association:
#app/models/coach.rb
class Coach < ActiveRecord::Base
has_and_belongs_to_many :categories
end
#app/models/category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :coaches
end
This, as opposed to the has_many :through relationship, does most of the legwork for you. You were correct in setting up your join_table as you did:
The importance of getting this right is that each time you CRUD either your Coach or Category objects, you'll have access to their associated data through the :categories and :coaches methods respectively.
Thus, you'll be able to populate the data like this:
#config/routes.rb
resources :coaches #-> url.com/coaches/new
#app/controllers/coaches_controller.rb
class CoachesController < ApplicationController
def index
#coaches = Coach.all
end
def new
#coach = Coach.new
end
def create
#coach = Coach.new coach_params
end
private
def coach_params
params.require(:coach).permit(:name, :email, :university, :batch, :phone, :categories)
end
end
This will then allow you to make the following view:
#app/views/coaches/new.html.erb
<%= form_for #coach do |f| %>
<%= f.text_field :name %>
<%= f.email_field :email %>
<%= f.text_field :university %>
<%= f.text_field :batch %>
<%= f.text_field :phone %>
<%= f.collection_select :categories, Category.all, :id, :name %>
<%= f.submit %>
<% end %>

rails How to save a serialized hash

I have a serialized object :address in Hotel model and I don't know how to save it properly in the DB. I have the following:
#model hotel
class Hotel < ActiveRecord::Base
belongs_to :user
serialize :address, Hash
end
...and view 'new'
<%= form_for(#hotel) do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :stars %>
<%= f.text_field :stars %>
<%= f.label :room, "Room description" %>
<%= f.text_area :room, size: "20x10" %>
<%= f.label :price %>
<%= f.number_field :price %>
<%= f.fields_for :address do |o| %>
<%= o.label :country %>
<%= o.text_field :country %>
<%= o.label :state %>
<%= o.text_field :state %>
<%= o.label :city %>
<%= o.text_field :city %>
<%= o.label :street %>
<%= o.text_field :street %>
<% end %>
<%= f.submit "Create hotel", class: "btn btn-large btn-primary" %>
<% end %>
With this code what I get is: hotel address nil...
Okay.. We will go another way. After googling much I came to this code:
# hotel.rb model
class Hotel < ActiveRecord::Base
class Address
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :country, :state, :city, :street
def persisted?; true end
def id; 1 end
def self.load json
obj = self.new
unless json.nil?
attrs = JSON.parse json
obj.country = attrs['country']
obj.state = attrs['state']
obj.city = attrs['city']
obj.street = attrs['street']
end
obj
end
def self.dump obj
obj.to_json if obj
end
end
belongs_to :user
serialize :address, Address
end
and the same view new.html.erb
The result is: Address:0xb0e530c
So, nothing saves in the database... I don't know what to try next, I'll appreciate any help. Didn't know that serialized object will cause so much problems to me.
THANKS!
PS Here's hotels_controller.
class HotelsController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update, :destroy]
def new
#hotel = Hotel.new
end
def index
#hotels = Hotel.paginate(page: params[:page])
end
def show
#hotel = Hotel.find(params[:id])
end
def create
#hotel = current_user.hotels.build(hotel_params)
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
private
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price, :address)
end
end
First thing on your migration file make sure that you are saving your fields as a test like
def self.up
add_column : hotels, : address, :text
end
Then Rails will convert it into YAML / Hash for you (and perform proper serialization).
Wish you the best of luck.
PS take a look at https://stackoverflow.com/a/6702790/1380867

How should I use rails and simple_form for nested resources?

I'm trying to create one resource with another nested resource at the same time. I'm using Rails4 and simple_form 3.0.0rc. Here is my code.
Models:
class User < ActiveRecord::Base
has_one :profile
accepts_nested_attributes_for :profile
end
class Profile < ActiveRecord::Base
belongs_to :user
end
Controller:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_profile
end
def create
user = User.new user_params
user.save
redirect_to root_url
# #par =params
end
private
def user_params
params.require(:user).permit(:email, profile_attributes: [:name])
end
end
View (form for new user)
<%= simple_form_for #user do |f| %>
<%= f.input :email %>
<%= simple_fields_for :profile do |p| %>
<%= p.input :name %>
<% end %>
<%= f.submit %>
<% end %>
When I submit the form, the create action receives this params:
{"utf8"=>"✓",
"authenticity_token"=>"dJAcMcdZnrtTXVIeS2cNBwM+S6dZh7EQEALZx09l8fg=",
"user"=>{"email"=>"vasily.sib#gmail.com"},
"profile"=>{"name"=>"Vasily"},
"commit"=>"Create User",
"action"=>"create",
"controller"=>"users"}
And after calling user_params the only thing that left is
{"email"=>"vasily.sib#gmail.com"}
And, as you can see, there is nothing about profile, so no profile will be created.
What am I doing wrong?
Use f.simple_fields_for instead of simple_fields_for:
<%= f.simple_fields_for :profile do |p| %>
<%= p.input :name %>
<% end %>
In my case I had the object "book" which belongs to "tour" and "tour" has_many "books".
In the "BookController" in the method "new" I find the tour and initialize the book object:
#tour = Tour.find(params[:tour_id])
#book = Book.new
This is the partial form to create a book: _form.html.erb
<%= simple_form_for [#tour, #book] do |f| %>
<%= f.input :name, label: "Name"%>
<%= f.input :NoReservations, label: "Number of Reservations" %>
<%= f.input :email, label: "Email" %>
<h3>Num of available places</h3>
<%= f.button :submit %>
<% end %>

Resources