Rails polymorphic commenting with permalink/token urls

112 Views Asked by At

In my app I have a commenting system that's largely based off of this railscast. Now in my models I'm changing the to_param to a random string so the id isn't in the url. But then that breaks commenting.

status.rb

class Status < ActiveRecord::Base
    attr_accessible :content, :member_id, :document_attributes, :permalink
    belongs_to :member 
    belongs_to :document
    has_many :comments, as: :commentable, dependent: :destroy

    before_create :make_it_permalink

    accepts_nested_attributes_for :document

    def to_param
        permalink
    end

    private

    def make_it_permalink
        # this can create permalink with random 12 digit alphanumeric
        self.permalink = SecureRandom.hex(12)
    end

end

statuses_controller.rb

class StatusesController < ApplicationController

before_filter :authenticate_member!, only: [:index, :new, :create, :destroy] 
before_filter :find_member

rescue_from ActiveRecord::RecordNotFound do
    render file: 'public/404', status: 404, formats: [:html]
end

def index
    @statuses = Status.order('created_at desc').page(params[:page]).per_page(21)
    respond_to do |format|
      format.html # index.html.erb
      format.js
    end
end

def show
    @status = Status.find_by_permalink(params[:id])
    @commentable = @status
    @comments = @commentable.comments.order('created_at desc').page(params[:page]).per_page(15)
    @comment = @commentable.comments.new
    respond_to do |format|
      format.html # show.html.erb
      format.json { redirect_to profile_path(current_member) }
    end
end

def new
    @status = Status.new
    @status.build_document

    respond_to do |format|
      format.html # new.html.erb
      format.json { render json: @status }
      format.js
    end
end

def create
    @status = current_member.statuses.new(params[:status])

    respond_to do |format|
      if @status.save
        @activity = current_member.create_activity(@status, 'created')
        format.html { redirect_to :back }
        format.json
        format.js
      else
        format.html { redirect_to profile_path(current_member), alert: 'Post wasn\'t created. Please try again and ensure image attchments are under 10Mbs.'  }
        format.json { render json: @status.errors, status: :unprocessable_entity }
        format.js
      end
    end
end

def destroy
    @status = current_member.statuses.find(params[:id])
    @activity = Activity.find_by_targetable_id(params[:id])
    @commentable = @status
    @comments = @commentable.comments
    if @activity
      @activity.destroy
    end
    if @comments
      @comments.destroy
    end 
    @status.destroy

    respond_to do |format|
      format.html { redirect_to profile_path(current_member) }
      format.json { head :no_content }
    end
end

private

def find_member
    @member = Member.find_by_user_name(params[:user_name])
end 

def find_status
    @status = current_member.statuses.find_by_permalink(params[:id])
end  

end

comments_controller.rb

class CommentsController < ApplicationController

before_filter :authenticate_member!
before_filter :load_commentable
before_filter :find_member

def index
    redirect_to root_path
end

def new
    @comment = @commentable.comments.new
end

def create
    @comment = @commentable.comments.new(params[:comment])
    @comments = @commentable.comments.order('created_at desc').page(params[:page]).per_page(15)
    @comment.member = current_member
    respond_to do |format|
      if @comment.save
        format.html { redirect_to :back }
        format.json
        format.js
      else
        format.html { redirect_to :back }
        format.json
        format.js
      end
    end 
end

def destroy
    @comment = Comment.find(params[:id])
    respond_to do |format|
      if @comment.member == current_member || @commentable.member == current_member
        @comment.destroy
        format.html { redirect_to :back }
        format.json
        format.js
      else
        format.html { redirect_to :back, alert: 'You can\'t delete this comment.' }
        format.json
        format.js
      end
    end 
end

private

# def load_commentable
#       resource, id = request.path.split('/')[1,2] # photos/1/
#       @commentable = resource.singularize.classify.constantize.find(id) # Photo.find(1)
# end 

# alternative option:
def load_commentable
    klass = [Status, Medium, Project, Event, Listing].detect { |c| params["#{c.name.underscore}_id"] }
    @commentable = klass.find(params["#{klass.name.underscore}_id"])
end

#def load_commentable
#  @commentable = params[:commentable_type].camelize.constantize.find(params[:commentable_id])
#end

def find_member
    @member = Member.find_by_user_name(params[:user_name])
end 

end

The problem lies in the load_commentable method in the comments_controller. I've tried a couple different variations of the method but the second one works best for my app and it was working when the url's had their id's in them. But since I overwrote the to_param to use my random permalink commenting stopped working because it's trying to find theid where it equals the permalink. Since it seems to try to find the id through the url, how do I pass the the actual id and not the permalink or how do I find commentable by it's permalink instead of id?

1

There are 1 best solutions below

1
pixelearth On BEST ANSWER

It's hard to tell if your param will always be the value of id or always be the permalink, or will sometimes be an id and sometimes a permalink.

If it will always be a permalink, then do:

@commentable = klass.find_by_permalink(params["#{klass.name.underscore}_id"])

instead of

@commentable = klass.find(params["#{klass.name.underscore}_id"])

If it is sometimes id and sometimes other, then you will need make logic to determine which is needed based on the class.