I have a question about many-to-many associations in Ruby on Rails.
I have 3 models in my app : Topic, Meeting and Todo associated with a manu-to-many association.
class Todo < ApplicationRecord
belongs_to :topic
belongs_to :meeting
end
then
class Meeting < ApplicationRecord
has_many :todos
end
and
class Topic < ApplicationRecord
has_many :todos
end
I made my routes and controller to be able to create new todos via a meeting :
Rails.application.routes.draw do
resources :meetings, only: [:index, :show, :new, :create, :edit, :update] do
resources :todos, only: [:index, :new, :create]
end
resources :todos, only: [:index, :show, :edit, :update, :destroy]
end
and
class TodosController < ApplicationController
def new
@topic = Topic.find(params[:topic_id])
@todo = Todo.new
end
def create
@todo = Todo.new(todo_params)
@meeting = Meeting.find(params[:meeting_id])
@todo.meeting = @meeting
@todo.save
redirect_to meeting_path(@meeting)
end
private
def todo_params
params.require(:todo).permit(:topic_id, :meeting_id, :note, :deadline, :title)
end
end
and my view :
<h3><%= @meeting.date %></h3>
<%= simple_form_for [@meeting, @todo] do |f| %>
<%= f.input :title %>
<%= f.input :note %>
<%= f.date_field :deadline %>
<%= f.association :topic, label_method: :nom, value_method: :id %>
<%= f.submit "Add a todo" %>
<% end %>
My problem is that I want to be able to create todo via topics aswell and when I add my routes :
resources :topics, only: [:index, :show, :new, :create] do
resources :todos, only: [:index, :new, :create]
end
When I tried to complete my controller and test it, it seems to be tricky. If I add:
@topic = Topic.find(params[:topic_id])
Then it tells me that it needs a meeting...
Any idea ?
You can create the different routes with:
resources :meetings do
resources :todos, only: [:index, :new, :create]
end
resources :topics do
resources :todos, only: [:index, :new, :create]
end
You can avoid duplication by using routing concerns:
concerns :todoable do
resources :todos, only: [:index, :new, :create]
end
resources :topics, concerns: :todoable
resources :meetings, concerns: :todoable
In your controller you can check for the presences of the meeting_id
or topic_id
parameters:
class TodosController < ApplicationController
before_action :set_parent
def new
@todo = Todo.new
end
def create
@todo = @parent.todos.new(todo_params)
if @todo.save
redirect_to @parent
else
render :new
end
end
private
def parent_class
@parent_class ||= if params[:meeting_id].present?
Meeting
else if params[:topic_id].present?
Topic
else
# raise an error?
end
end
def set_parent
id = params["#{parent_class.model_name.param_key}_id"]
@parent = parent_class.find(id)
end
def todo_params
params.require(:todo)
.permit(:topic_id, :meeting_id, :note, :deadline, :title)
end
end
<%= simple_form_for [@parent, @todo] do |f| %>
<%= f.input :title %>
<%= f.input :note %>
<%= f.date_field :deadline %>
<%= f.association :topic, label_method: :nom, value_method: :id if @parent.is_a?(Meeting) %>
<%= f.association :meeting, label_method: :nom, value_method: :id if @parent.is_a?(Topic) %>
<%= f.submit "Add a todo" %>
<% end %>