I'm learning Rails and would like to be able to use the same form on the same page to add an element (here a "dose") or edit an existing one by prefilling the form (after a page refresh, I don't need to do it dynamically).
Here is a screenshot of the page (views/cocktails/show.html.erb)
Creating a new "dose" works fine, but when trying to edit Rails raises the "DosesController#edit is missing a template for this request format and variant" error.
As I'm trying to really understand the routes logic, I'm doing this just for the sake of learning, even if this is probably a bad practice, and I'd like to achieve this with Rails only.
views/doses/_form.html.erb:
I'm putting the logic into the form partial to decide whether to create or update the @dose:
<% if @dose.new_record? %>
<%= simple_form_for [@cocktail, @dose] do |f| %>
<%= f.input :description %>
<%= f.association :ingredient %>
<%= f.button :submit %>
<% end %>
<% else %>
<%= simple_form_for @dose, url: url_for("doses#update") do |f| %>
<%= f.input :description %>
<%= f.association :ingredient %>
<%= f.button :submit %>
<% end %>
<% end %>
views/cocktails/show.html.erb:
There I'm calling a "new or edit" path helper, passing it the cocktail id and dose id, as dose belongs to one cocktail.
<% @cocktail.doses.each do |dose| %>
<li>
<h4><%= dose.description %> - <%= dose.ingredient.name %></h4>
<%= link_to "<i class=\"fas fa-pencil-alt\"></i>".html_safe, new_or_edit_dose_path(@cocktail.id, dose.id) %>
<%= link_to "<i class=\"far fa-trash-alt\"></i>".html_safe, dose_path(dose.id), method: :delete, data: {confirm: "Are you sure?"} %>
</li>
<% end %>
</ul>
<%= render "doses/form" %>
<%= link_to "Back", cocktails_path %>
</div>
routes.rb
This path helper is bound to a GET route, associated with the edit action of the doses controller. I would therefore not be using the doses#new route action at all.
root to: "cocktails#index"
resources :cocktails, only: [:index, :show, :new, :create] do
resources :doses, only: [ :create]
end
resources :doses, only: [ :update, :destroy]
get "cocktails/:id/doses/:dose_id", to: "doses#edit", as: :new_or_edit_dose
doses_controller.rb
def edit
@cocktail = Cocktail.find(params[:id])
@dose = Dose.find(params[:dose_id])
end
def update
if @dose.update(set_params)
redirect_to cocktail_path(@dose.cocktail)
else
render(:edit)
end
end
def set_params
params.require(:dose).permit(:description, :ingredient_id)
end
My comprehension of the problem
I guess the problem is that I'm using a cocktails/:id/doses/:id route, but displaying the form on a cocktails/:id/show view. I've tried to use a redirect_to at the end of doses#edit and passing it the params, but it didn't worked.
Could someone tell me if it's feasible, and give me a hint on what I should be looking for?
Regarding
... but when trying to edit Rails raises the DosesController#edit is missing a template for this request format and variant
... means that you are missing the file app/views/doses/edit.html.erb
. So just create that file, or... because you said...
I guess the problem is that I'm using a cocktails/:id/doses/:id route, but displaying the form on a cocktails/:id/show view
... which if understood it correctly, you want this url cocktails/:id/doses/:id
to show a page exactly as your cocktails/:id/
(which presumably mapped to your cocktails#show
page, then you can just do the following:
app/controllers/doses_controller.rb:
# ...
def edit
@cocktail = Cocktail.find(params[:id])
@dose = Dose.find(params[:dose_id])
render 'cocktails/show'
# rails by default has an invisible line at the end of the actions, unless `render` is called manually like line above. But removing the line above, you'll get something like below
# render 'CONTROLLER_NAME/ACTION_NAME.FORMAT.TEMPLATE_ENGINE'
# so in this specific case:
# CONTROLLER_NAME == doses
# ACTION_NAME == edit
# FORMAT == html
# TEMPLATE_ENGINE == erb
# which translates to:
# render 'doses/edit.html.erb'
# which you can also do like the following
# render 'edit.html.erb' # because same controller name as the template being called
# which you can also do like the following
# render 'edit' # because it automatically identifies what format using the value of `request.format`, and what default template engine your Rails app is configured to use
# ... and this is the reason why you got that error "DosesController#edit is missing a template...", because app/views/doses/edit.html.erb does not exist, which by default is being rendered
Please clarify first why you want the following route, and I'll see if I have a better solution, in my opinion:
get "cocktails/:id/doses/:dose_id", to: "doses#edit", as: :new_or_edit_dose
... I asked this because normally a "create or update" / "new or edit" request is routed against some unique attribute identifier. For example, in your case, the unique attribute identifier seems to be :dose_id
, because you "create" a new Dose
record if the :dose_id
does not yet exist, or you "update" the Dose
record if it already exists which has the id
value equal to :dose_id
. Now, this is my opinion but I do not think it is a good idea to create a Dose
record from a manually-set "id", because you normally let the database auto-assign the primary key id
values.
...To explain my point, for example, to simulate the said route above, say I open this URL in the browser: "/cocktails/1/doses/1"
, so far so good assuming that Dose(id: 1)
already exists, then I will just see on the page a form to UPDATE the Dose
. However, let's say I open "/cocktails/1/doses/9999999999"
, and let's say that Dose(id: 9999999999) does not exist yet
, then I will see a form to CREATE the Dose
... and this is still valid at this point. The problem occurs when you already deleted the Dose
record. So let's say, Dose(id: 1)
has already been deleted, and then I tried opening "/cocktails/1/doses/1"
again, because it's already been deleted, then I will see a form to CREATE the Dose
(which is still ok), but becomes problematic because you are reusing the id
values which should be a "primary key" and should uniquely identify a resource, because your associations / models that depend on this unique id value would potentially be messed up: like for example I have a Car
record of attributes {name: 'Porsche', category_id: 23}
that belongs_to a Category(23)
record that has an attribute {name: 'vehicle'}
, and then somehow out of the blue Category(23)
has been destroyed, and then recreated with the same ID! but this time the attributes created let's say has been become {name: 'fruit'}
. Now, my Porsche Car becomes a Fruit!!
However, I can think of some reasons why you'd intentionally do this (i.e. if you want to treat the id
value as uuid
which can potentially never be reused programatically), but my thoughts above just in case you are not aware of yet with these possible consequences.