This service object allows users without a session to create a comment, and register OR login at the same time.
If a user is signed out he must enter an email and a password along with the comment.
The service checks if a user exists using the email, then attempts to authenticate the user. Otherwise, it creates a new user.
Three code smells:
- User errors are added to the comment. There is no user object in the view, just a params hash with that name.
- If user validation fails, comment validation does not trigger, meaning if the comment was also blank they need another round trip to find out. Probably not significant though.
- User data is not repopulated on validation failure.
Question: I'm proud of writing a nifty object like that, but it stills feels a little 'wrong-ish' if there is such a word. Is there a better way to handle this type of scenario?
class CommentService def self.by(params, user) if user new params, user else new params, find_or_new(params) end end def self.find_or_new(params) User.find_by(email: params[:user][:email]) || User.new end attr_reader :params, :comment, :user def initialize(params, user) @params, @user = params, user @comment = Comment.new(comment_params) end def save if user.new_record? self.user.attributes = user_params user.save ? persist : add_user_errors else if params[:user].present? user.authenticate(params[:user][:password]) ? persist : add_auth_error else persist end end end def post @post ||= Post.find(params[:post_id]) end private def add_user_errors user.errors.each do |k,v| comment.errors.add(k, v) end false end def add_auth_error comment.errors[:base] << "Invalid email or password." false end def user_params params.require(:user).permit(:name, :email, :password) end def comment_params params.require(:comment).permit(:text) end def persist comment.author = user comment.post = post comment.save end end
def create service = CommentService.by(params, current_user) @post = service.post if service.save redirect_to @post else @comment = service.comment render 'new' end end
= form_for [@post, @comment] do |f| = f.text_area :comment / No user object here. Just tossing a user hash into the params. - unless signed_in? = fields_for :user do |uf| = uf.text_field :email = uf.password_field :password = f.submit