Reputation: 479
I have set up a polymorphic association in my app. A picture belongs to both a scoreboard model and a user model as pictureable. Each scoreboard and user has_one picture. I also have nested routes where pictures resource is nested inside a user and scoreboard resource.
Routes File for the users:
resources :users do
resources :picture
end
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :conversations, only: [:index, :show, :destroy] do
member do
post :reply
post :restore
post :mark_as_read
end
collection do
delete :empty_trash
end
end
Routes file for the scoreboard:
resources :scoreboards do
member do
put :favourite
get :deleteteams
get :deleteschedules
end
resources :invitations, only: [:new, :create]
resources :comments
resources :teams, only: [:edit, :create, :destroy, :update]
resources :schedules
resources :picture
end
I have setup a picture controller. The code is given below.
class PictureController < ApplicationController
before_filter :load_pictureable
def new
@picture = @pictureable.build_picture
end
def create
end
def destroy
end
private
def picture_params
params.require(:picture).permit(:picture)
end
def load_pictureable
klass = [User, Scoreboard].detect { |c| params["#{c.name.underscore}_id"] }
@pictureable = klass.find(params["#{klass.name.underscore}_id"])
end
end
My new form is given below:
<div id= "uploadphoto">
<%= form_for([@pictureable, @picture], url: scoreboard_picture_path, html: {multipart: true}) do |f| %>
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png', id: "files", class: "hidden" %>
<label for="files" id="picture"><h4 id="picturetext">Click Here to Upload Picture</h4></label>
<div class="modal" id="modal-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">Position and Crop your Image</div>
<div class="modal-body">
<div class="upload-preview">
<img id="image" /></img>
</div>
<%= f.submit "upload photo" %>
</div>
<div class="modal-footer"> Click Anywhere Outside the box to Cancel</div>
</div>
</div>
</div>
<% end %>
</div>
The problem I am having is with the URL. This gives me the following error.
No route matches {:action=>"show", :controller=>"picture", :scoreboard_id=>"13"} missing required keys: [:id]
I am not sure why it redirects me to the show action, I assume its probably the nested routes.I don't know what the problem is. I thought I had resolved it by passing the following URL new_scoreboard_picture_path
. However, when I press upload photo, It would take me back to the same URL. This makes sense because I am explicitly stating the URL in the new form. However, that is incorrect. It would be a great help if someone could help me figure out what the issue is. I have only included the relevant code. However, if i am missing anything please do let me know. Any explanation as to why I am getting this error would also clarify my though process. This is the first time working with polymorphic associations. Any sort of help would be great!!
Upvotes: 1
Views: 247
Reputation: 102433
I would use inheritance and two separate controllers instead:
# the base controller
class PicturesController < ApplicationController
before_action :load_pictureable
def new
@picture = @pictureable.build_picture
end
def create
@picture = @pictureable.build_picture
if @picture.save
do_redirect
else
render_errors
end
end
def destroy
# ...
end
private
def picture_params
params.require(:picture).permit(:picture)
end
# Finds the picturable based on the module name of the class
# for examples `Pets::PicturesController` will look for
# Pet.find(params[:pet_id])
def find_pictureable
# this looks at the class name and extracts the module name
model_name = self.class.deconstantize.singularize
@pictureable = model_name.constanize.find(params["#{model_name}_id"])
end
# subclasses can override this to whatever they want
def do_redirect
redirect_to @pictureable
end
# subclasses can override this to whatever they want
def render_errors
render :new
end
end
Then setup the routes:
resources :users do
resource :picture, controller: 'users/pictures'
end
resources :scoreboards do
resource :picture, controller: 'scoreboards/pictures'
end
Since a user/scoreboard can have only one picture we are using resource
and not resources
. This sets up the routes for singular resource. Use rake routes
to see the difference for yourself. For example you create a picture with POST /users/:user_id/picture
.
We then setup the child controllers:
# app/controllers/users/pictures_controller.rb
class Users::PicturesController < PicturesController
end
# app/controllers/scoreboards/pictures_controller.rb
class Scoreboards::PicturesController < PicturesController
end
This allows a high degree of customization without a jumble of if/else
or switch
statements. Also this safeguards against a malicious user passing params[:user_id]
to manipulate a users picture. You should never use user input to determine classes or database tables.
Also you do not need to explicitly specify the form url:
<%= form_for([@pictureable, @picture], html: {multipart: true}) do |f| %>
Will use users_picture_path(user_id: @pictureable.id)
or the same for scoreboards.
Upvotes: 0