@halogenandtoast
Just like you created a concern for the model I created a concern for the controllers called Shoutable.
But all I could do is
module Shoutable
def build_shout(content)
current_user.shouts.build(content: content)
end
end
class TextShoutsController < ApplicationController
include Shoutable
def create
content = build_content
shout = build_shout(content)
if shout.save
else
flash.alert = "Could not shout"
end
redirect_to dashboard_path
end
The create method is also duplicated but should I move that too because redirect_to and the create method I feel should be in the controller
I tend to agree that the redirect_to and the create action should be in the controller. One thing you might consider is trying to merge the TextShoutsController and PhotoShoutsController into 1 controller. You can do this if you update your form to pass along the name of the content class and use a sprinkling of metaprogramming to get the class (i.e. TextShout or PhotoShout). In addition to that, strong_parameters has a method #permit! which allows you to permit any attributes. Since weād be okay permitting any of the attributes on the content class this makes it so we donāt have to keep track of which content classes contain what fields. Iād see how far you can get with trying to merge TextShoutsController and PhotoShoutsController into a single ShoutsController.
And the controller app/controllers/shouts_controller.rb is the only controllers for shouts refactored like this:
class ShoutsController < ApplicationController
def show
@shout = Shout.find(params[:id])
end
def create
content = build_content
shout = current_user.shouts.build(content: content)
unless shout.save
flash.alert = "Could not shout."
end
redirect_to dashboard_path
end
private
def shout_type
params[:shout_type]
end
def build_content
eval("#{shout_type}Shout".classify).new(eval("#{shout_type}_shout_parameters"))
end
def text_shout_parameters
params.require(:text_shout).permit(:body)
end
def photo_shout_parameters
params.require(:photo_shout).permit(:image)
end
end
Even I tried a solution but with a similar project.
You can use permit! to do away with the parameters method. And use params[:content_type]. This was suggested by @halogenandtoast. Even I am waiting for his reply.
Yes, I used shout_type, instead of content_type, but the principle is the same as yours. I think your solution is better than mine because it works also when editing the form (you take the class name, while I have hardcoded the type).
If itās the good approach Iāll refactor following your approach. Do you have a public repo for your project? It would be interesting for me to take a look. Thank you!
The usage of permit! should be sparingly but I think in the case of the TextShout and PhotoShout they represent the part of the shout the user has control over and therefor I am okay with using permit!. The other thing I like doing here is changing the form_for a bit to something like:
<%= shout_form_for(TextContent.new) do |form| %>
<%= form.text_field :body, placeholder: "Shout here" %>
<%= form.submit "Shout!" %>
<% end %>
And then I use the following helper:
module ShoutHelper
def shout_form_for(content, &block)
form_for(Shout.new(content: content)) do |form|
form.hidden_field(:content_type) +
form.fields_for(:content, &block)
end
end
end
And then in my controller:
def create
shout = current_user.shouts.create(shout_params)
redirect_to dashboard_path
end
private
def shout_params
{ content: shout_content }
end
def shout_content
content_class.new(content_params)
end
def content_class
params[:shout][:content_type].constantize
end
def content_params
params[:shout][:content].permit!
end
I use this format because itās pretty versatile and will grow well with the application if the existing content types change.
Yes, thank you very much for sharing the refactor.
I donāt get how you can pass the TextContent object to the content of the Shout and everything works magically
I had the same doubt in the ShoutsController#create, I donāt understand where you see the match between content and content_type. I donāt know if itās clear my doubt. Thanks!
assigns the content to the new Shout, so this Shoutās content_type field will take the TextShout or PhotoShout value, according to the argument passed to shout_form_for.
In the controller, the content_class method takes the content_type param from the hidden field and uses constantize to generate the corresponding class (i.e: converts a "TextShout" string to a TextShout class). That class is used in shout_content to generate the object that will be assigned as content to the new Shout.
It was also unclear the polymorphic association. Rails works out automatically the content_type and content_id just pasing the TextContent.new object. I was also missing this last bit of magic!