Call controller from the view of another controller - ruby-on-rails

i have a restaurant controller, and in the show method i render a form of another controller (dishes)
#new.html.erb (from dishes controller)
<%= render 'dishes/dish_form' %>
#show.html.erb (from restaurant controller)
<%= render template: 'dishes/new' %>
and this is the form:
<%= form_for #dish, :url => { :controller => "dishes", action_name => "create" } do |f| %>
<div class="field">
<%= f.label :dish_name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div class="field">
<%= f.label :image %>
<%= f.file_field :avatar %>
</div>
<div class="actions">
<%= f.submit 'Add new dish' %>
</div>
<% end %>
but when i try so add a dish, i have this error
this is my dishes controller:
def new
#dish = Dish.new
end
def create
#dish = Dish.new(dish_params)
respond_to do |format|
if #dish.save
format.html { redirect_to #restaurant, notice: 'dish was successfully created.' }
format.json { render action: 'show', status: :created, location: :restaurant }
else
format.html { render action: 'show', location: :restaurant }
format.json { render json: #restaurant.errors, status: :unprocessable_entity }
end
end
end
private
def dish_params
params.require(:dish).permit(:avatar, :name, :description)
end
and this are my models:
class Restaurant < ApplicationRecord
has_many :dish, inverse_of: :restaurant
accepts_nested_attributes_for :dish
end
class Dish < ApplicationRecord
belongs_to :restaurant
end
im learning rails so maybe is a dumb error but im stuck

you should try to this to the controller action where the form is rendered
#dish = Dish.new
as you directly call the 'create' action rails is missing the instance variable Dish.new. Therefore the error message. Normally rails works like this:
def new
#dish = Dish.new
end
and than you call the create action on 'submit'.
However, as it looks like you call the form from the #show action just add this code there and you will be fine. May be not the best solution but it will work like that. Add to #show
#dish = Dish.new
Sorry mate. Yes #dish is correct and not :dishes.

OK.
My latest guess is that you want the restaurants to be able to create many dishes.
So you should set up your models accordingly:
#restaurants_model
has_many :dishes
#dishes_model
belongs_to :restaurant
next is that you add a column called restaurant_id to your dishes table
t.string :restaurant_id
than in your restaurant controller #show
def show
end
if you use the routes as normal, e.g.
resources :restaurants
resources :dishes
the show action of the restaurant controller should give you a url that looks like this: localhost:3000/restaurants/1
where the 1 is the id of the restaurant.
This id you want to save to the dish by adding a hidden field
=f.hidden_field :restaurant_id, value: #restaurant.id
you need to permit the restaurant_id in the dishes controller params, e.g. dish_params. Just add
:restaurant_id
and it should be fine. This way the id will be saved to the dishes table and you can later call it from there.
This gives you the chance to call #restaurant.dishes which will show all dishes of that restaurant.
If you just want to redirect back you can use redirect :back
Otherwise, you can try to integrate a helper to get the right restaurant using the hidden restaurant_id field

Related

How to avoid using a hidden field for user_id in a Rails nested form

I feel like this should be an easy thing to do in Rails, but all of the examples of nested forms in Rails do not take into account the fact that most nested forms also need to pass the current_user when creating new objects through a nested form.
The only way I can get this to work at the moment is by passing a hidden field such as <%= form.hidden_field :user_id, value: current_user.id %>.
For my specific example, I have a model called "Result" that has many "Lessons" and I'd like to create new lessons through the Result form without passing a hidden :user_id.
This seems unsafe because someone could edit that hidden field in the browser and then submit the form thus associating the submission with a different user. The current_user.id seems like the type of thing you don't want to embed in the html as a hidden field.
So how do you create the association between the nested objects and the current_user without putting that hidden field in the form?
FYI, I'm using the GoRails nested form with stimulus style javascript to add and remove lessons from the result form. (Here's the source code for that example.) Here are the relevant parts of my code:
models/result.rb
class Result < ApplicationRecord
belongs_to :user
has_many :lessons, inverse_of: :result
accepts_nested_attributes_for :lessons, reject_if: :all_blank, allow_destroy: true
end
models/lesson.rb
class Lesson < ApplicationRecord
belongs_to :user
belongs_to :result
end
controllers/results_controller.rb
class ResultsController < ApplicationController
before_action :set_result, only: [:show, :edit, :update, :destroy]
def new
#result = Result.new
#result.lessons.new
end
def create
#result = current_user.results.new(result_params)
respond_to do |format|
if #result.save
format.html { redirect_to #result, notice: 'Result was successfully created.' }
else
format.html { render :new }
end
end
end
private
def set_result
#result = Result.find(params[:id])
end
def result_params
params.require(:result).permit(:prediction_id, :post_mortem, :correct,
lessons_attributes: [:user_id, :id, :summary, :_destroy])
end
end
controllers/lessons_controller.rb
class LessonsController < ApplicationController
before_action :set_lesson, only: [:show, :edit, :update, :destroy]
# GET /lessons/new
def new
#lesson = Lesson.new
end
def create
#lesson = current_user.lessons.new(lesson_params)
respond_to do |format|
if #lesson.save
format.html { redirect_to #lesson, notice: 'Lesson was successfully created.' }
else
format.html { render :new }
end
end
end
private
def set_lesson
#lesson = Lesson.find(params[:id])
end
def lesson_params
params.require(:lesson).permit(:result_id, :summary)
end
end
views/results/_form.html.erb
<%= form_with(model: result, local: true) do |form| %>
<h3>Lessons</h3>
<div data-controller="nested-form">
<template data-target="nested-form.template">
<%= form.fields_for :lessons, Lesson.new, child_index: 'NEW_RECORD' do |lesson| %>
<%= render "lesson_fields", form: lesson %>
<% end %>
</template>
<%= form.fields_for :lessons do |lesson| %>
<%= render "lesson_fields", form: lesson %>
<% end %>
<div class="pt-4" data-target="nested-form.links">
<%= link_to "Add Lesson", "#",
data: { action: "click->nested-form#add_association" } %>
</div>
</div>
<div class="form-submit">
<%= form.submit "Save" %>
</div>
<% end %>
views/results/_lesson_fields.html.erb
<%= content_tag :div, class: "nested-fields", data: { new_record: form.object.new_record? } do %>
# This hidden field seems unsafe!
<%= form.hidden_field :user_id, value: current_user.id %>
<div class="pb-8">
<%= form.text_area :summary %>
<%= link_to "Remove", "#",
data: { action: "click->nested-form#remove_association" } %>
</div>
<%= form.hidden_field :_destroy %>
<% end %>
I'm sure this is a common problem in Rails but I can't find any tutorials online that have the user_id as a part of the nested fields example. Any help is much appreciated!
Personally, since setting the current_user id is something the controller should care about, I would iterate over all the lessons and set the user_id value there.
def create
#result = current_user.results.new(result_params)
#result.lessons.each do |lesson|
lesson.user ||= current_user if lesson.new_record?
end
... the rest ...
Having a hidden field is a security risk, someone could edit it. I also don't like changing the params hash.
I don't think there is a great way to handle this automatically outside of the view. You would either have to inject the value unto the params or possible have a use default on the user association in Lesson that sets it from the Record's user (belongs_to :user, default: -> { result.user }). In these scenarios, I generally move outside of the default Rails flow and use a PORO, Form Object, service object, etc.
build form like this
<%= form.fields_for :lessons, lesson_for_form(current_user.id) do |lesson| %>
<%= render "lesson_fields", form: lesson %>
<% end %>
remove hidden user_id field you have added
update your result.rb file
class Result < ApplicationRecord
def lesson_for_form(user_id)
collection = lessons.where(user_id: user_id)
collection.any? ? collection : lessons.build(user_id: user_id)
end
end

Nested form not showing fields

I have a nested form based in a has_one relationship but it's not showing the fields.
What am I missing?
New action
def new
#doctor = Doctor.new
1.times { #doctor.build_schedule }
respond_to do |format|
format.html # new.html.erb
format.json { render json: #doctor }
end
end
_form
<%= simple_form_for(#doctor, :html => { class: "form-horizontal"}) do |f| %>
<%= f.input :name %>
<%= f.simple_fields_for :schedule do |builder| %>
<%= render 'days_checkboxes', :f => builder %>
<%= f.submit %>
<% end %>
Model
class Doctor < ActiveRecord::Base
has_one :schedule, dependent: :destroy
end
Do I have to set the build in other actions?
If there is no schedule associated object on your model instance, yes you will need to call build_schedule wherever you want to reference it. If you didn't do this, the form wouldn't render at all because it has nothing to display the fields for.

Controller and routes issues in my rails app

I have an app where users can create courses, and each course has_one syllabus. How could I go about configuring my courses and syllabuses (I know it's Syllabi but apparently Rails doesn't) controller, and my routes, so on a course's page there is a link to create or show the course's syllabus, and a link back to the course from the show syllabus page?
In my routes I have:
resources :courses do
resources :syllabuses
member do
put :enroll #this is so users can enroll in the course
end
end
Currently , so the course_id will be saved in the syllabus table in my courses_controller, I have:
def create_syllabus
#course = Course.find(params[:id])
#syllabus = #course.build_syllabus(params[:syllabus])
if #syllabus.save
redirect_to #syllabus, notice: "Successfully created syllabus."
else
render :new
end
end
then in my courses show page I have:
<section>
<% if (current_user.courses.includes(#course) ||
current_user.coursegroups.find_by_course_id_and_role(#course.id, "admin")) %>
<%= render 'create_syllabus' %>
<% end %>
</section>
then in my create_syllabus form (in my courses views folder) I have tried starting it off with:
# I have #course = Course.find(params[:id]) defined in show in the
#courses_controller
<%= form_for #course.create_syllabus do |f| %>
<%= form_for #course.syllabus.create_syllabus do |f| %>
<%= form_for #course.syllabus.create do |f| %>
and I get an undefined method error for each of those.
If you want to create a new syllabus in your show action of a specific course, you can add this to your controllers and views:
courses_controller.rb
#course = Course.find(params[:id])
# Build a new #syllabus object, only if there is none for the current course
unless #course.syllabus
#syllabus = #course.build_syllabus
end
views/courses/show.html.erb
# Show the syllabus name if there is one, or show the form to create a new one
<% if #course.syllabus.name %>
<p>Syllabus: <%= #course.syllabus.name %></p>
<% else %>
<p>Create Syllabus:</p>
<%= form_for([#course, #syllabus]) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<% end %>
syllabuses_controller.rb
def create
#course = Course.find(params[:course_id])
# Build new syllabus object based on form input
#syllabus = #course.build_syllabus(params[:syllabus])
if #syllabus.save
# redirect to /course/:id
redirect_to #course, notice: 'Syllabus was successfully created.' }
end
end
course.rb
class Course < ActiveRecord::Base
attr_accessible :name
has_one :syllabus
end
syllabus.rb
class Syllabus < ActiveRecord::Base
belongs_to :course
attr_accessible :name, :course_id
end
Some things that I left out but you should still include:
validations
rerendering form if something goes wrong
pulling things out into partials
fixing bad code like if #course.syllabus.name
pull out if/else logic into a helper
…

Rails: Updating Nested attributes - undefined method `to_sym' for nil:NilClass

Edit: Added the update action, and on what line the error occurs
Model:
class Match < ActiveRecord::Base
has_and_belongs_to_many :teams
has_many :match_teams
has_many :teams, :through => :match_teams
accepts_nested_attributes_for :match_teams, :allow_destroy => true
end
Controller:
def new
#match = Match.new
#match_teams = 2.times do
#match.match_teams.build
end
respond_to do |format|
format.html # new.html.erb
format.json { render json: #match }
end
end
def update
#match = Match.find(params[:id])
respond_to do |format|
if #match.update_attributes(params[:match])
format.html { redirect_to #match, notice: 'Match was successfully updated.' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: #match.errors, status: :unprocessable_entity }
end
end
end
Nested model:
class MatchTeam < ActiveRecord::Base
belongs_to :match
belongs_to :team
end
Association:
class Team < ActiveRecord::Base
has_and_belongs_to_many :matches
end
View:
<%= form_for(#match) do |f| %>
<%= f.fields_for :match_teams, #match_teams do |builder| %>
<%= builder.collection_select :team_id, Team.all, :id, :name, :include_blank => true %>
<% end %>
<% unless #match.new_record? %>
<div class="field">
<%= f.label :winning_team_id %><br />
<%= f.collection_select :winning_team_id, #match.teams, :id, :representation %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Params:
Processing by MatchesController#update as HTML
Parameters: {"utf8"=>"Ô£ô", "authenticity_token"=>"QIJChzkYOPZ1hxbzTZS8H3AXc7i
BzkKv3Z5daRmlOsQ=", "match"=>{"match_teams_attributes"=>{"0"=>{"team_id"=>"1", "
id"=>""}, "1"=>{"team_id"=>"3", "id"=>""}}, "winning_team_id"=>"3"}, "commit"=>"
Update Match", "id"=>"2"}
Creating a new match with 2 teams work fine, the edit view also shows the correct values, but the update action gives me this error.
undefined method `to_sym' for nil:NilClass
app/controllers/matches_controller.rb:65:in `block in update'
line 65: if #match.update_attributes(params[:match])
I've figured it out. I read that a join table like MatchTeams doesn't need an ID. I'm guessing this is true when not doing any nested forms. I redid my migration removing the exclusion of the id column, and now everything works fine. Don't we all love this stupid errors? :)
Without seeing the offending to_sym in your code, just know that the thing it's attached to has not been defined properly. If this is a variable such as #var.to_sym, you most likely:
Haven't set #var at all
Set it but it's returning nil because there are no matches (e.g. #var = #project.companies.first but #project has no companies tied to it).
You are missing a relevant bit of data in your params. If your to_sym is relying on data submitted through the form, it won't work if the user leaves out the bit of data you're assuming. In this case, you should test first to see if the data was entered before running .to_sym on it.

Cannot get nested form with a has_one association to work

I have these models:
class User < ActiveRecord::Base
has_one :city
accepts_nested_attributes_for :city
end
class City < ActiveRecord::Base
belongs_to :user
end
This controller action:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to(#user, :notice => 'User was successfully created.') }
format.xml { render :xml => #user, :status => :created, :location => #user }
else
format.html { render :action => "new" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
and this view:
<%= form_for :user,:url => users_path,:method => :post do |f| %>
<%= f.fields_for :city do |b| %>
<%= b.collection_select :id,City.all,:id,:name %>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I am trying to allow the user to select a city from the list of already added cities. I am trying to present him a select. The select part it works, but the generated html code for it, looks like this:
<select name="user[city][id]" id="user_city_id">
<option value="1">One</option>
<option value="2">Two</option>
</select>
Notice that it's name doesn't have attribute anywhere. So, when I try to save it, I get this error:
City(#37815120) expected, got ActiveSupport::HashWithIndifferentAccess(#32969916)
How can I fix this?
EDIT: there is some progress, I tried to change the fields_for to this:
<%= f.fields_for :city_attributes do |b| %>
<%= b.collection_select :id,City.all,:id,:name %>
<% end %>
and now, the html seems to generate correctly. But I get this error now:
Couldn't find City with ID=1 for User with ID=
I have no idea what to do next.
EDIT2: overriding the city_attributes= method seems to work:
def city_attributes=(attribs)
self.city = City.find(attribs[:id])
end
I don't know if it's the way to go, but it seems good.
Have a look at this question that seems similar to yours :
Rails 3: How does "accepts_nested_attributes_for" work?
Actually, since the Cities already exsit, I think there is no need for nested forms here.
Try Replacing
<%= f.fields_for :city_attributes do |b| %>
<%= b.collection_select :id,City.all,:id,:name %>
<% end %>
With
<%= f.collection_select :city, City.all,:id,:name %>
Updated afters comments
Could you change your relationship with (and update database scheme accordingly)
class User < ActiveRecord::Base
belongs_to :city
end
class City < ActiveRecord::Base
has_many :users
end
And then try using:
<%= f.collection_select :city_id, City.all,:id,:name %>
You could also do a
<%= f.collection_select :city_id, City.all, :id, :name %>
in your view and then add virtual attributes to your User model:
class User < ActiveRecord::Base
...
def city_id(c_id)
update_attribute(:city, City.find(c_id))
end
def city_id
city.id
end
end
This might not be very clean, since the associated City model is "saved" whenever assigning an ID to some_user.city_id. However, this solution keeps your controller and view nice and clean.
Note: you might also want to account for a blank ID being passed in to the setter method.
Try this
<%= f.select(:city_id, City.all.collect {|p| [ p.name, p.id ] }) %>

Resources