daspianist
daspianist

Reputation: 5495

Controller logic for on-click events used with link_to

I am interested making a feature similar to that of SO's, where an user who has asked a question could select an answer from a list of responses, but am stuck on how to handle the click events from the controller perspective.

Currently, I have the models Question, which has_many answers. Additionally, I have made an Selection model, which is meant to symbolize the answer selection, where each Question only could has_one selected answer.

I render each questions's answers along with the question in the question's show action. In my _answer partial, I want to place the following logic: "if the user who has asked the question is logged on, he could see links to 'unselect' the answer if the related selection has that answer's answer_id already stored (in other words, marking that this answer was previously selected), pr otherwise he could 'select' the answer which stores the answer's answer_id in selection. (I should mention that I adapted most of the code below from Rails tutorial here, where I (unfortunately) don't quite have a solid understanding how to use POST to match or assign attributes to an instance variable )

  <% if current_user?(answer.question.user) %>
    <% if current_user.selections.where(:answer_id => answer.id) == answer.id %>
      <%= link_to "unselect", selections_path(:selection => {:answer_id => nil}), :method => :post %>
    <% else %>
      <%= link_to "select", selections_path(:selection => {:answer_id => answer.id}), :method => :post %>
    <% end %>
  <% end %>

My roadblock is in the controller, where I'm stuck on how to use the create method to assign the @selection variable to a new answer_id if the user selects a new answer via the "select" link. Any help, or any guides on how to write on-click events would be much appreciated. Thanks!

Upvotes: 1

Views: 923

Answers (1)

m_x
m_x

Reputation: 12564

There's many different ways to do this, but i tend to think that you have a design problem here, so here's how i would do it.

I assume from what you said that your associations look like this :

 __________                                  ________
| Question |1                              *| Answer |
|          |<-------------------------------|        |
|          |1     * ___________ *          1|        |
|          |<------| Selection |----------->|________|
|          |       |           |            
|          |       |           |*          1 ________
|          |       |___________|----------->| User   |
|          |*                              1|        |
|__________|------------------------------->|________|

This visual representation clearly shows the problem : your Selection model is redundant, as it represents the fact that a question belongs_to a User (which we already know) ; it would not be redundant if there could be multiple selections for each couple of Question / User, but in practice we want that selection to be unique for each couple...

A belongs_to relation between Question and Answer would achieve the same thing that your Selection model does, and in fact it would do it better because you won't need all the logic to find the right selection, and to ensure it is unique / that the user is the rightfull owner of the question, etc.

So here's what i'd do :

  • In the Question model

    has_many   :answers,         inverse_of: :question
    belongs_to :accepted_answer, class_name: :answer, foreign_key: :accepted_answer_id
    
  • In the Answer model

    belongs_to :question, inverse_of: :answers
    
    def accepted?
      return false if new_record? 
      question.try( :accepted_answer_id ) == id
      # an alternative is to use question.try( :accepted_answer ) == self
    end
    
  • In your routes

    resources :questions do
      member do 
        # we use put because these are update actions
        put :accept_answer
        put :clear_accepted_answer
      end
    end
    
  • In your QuestionsController

    respond_to :js, only: [:accept_answer, :clear_accepted_answer]
    
    def accept_answer
      @question = Question.find( params[:id] )
    
      # ...cue some logic to ensure current_user 
      # has required rights to update the question
    
      if @question.update_attributes( accepted_answer_id: params[:answer_id] )
        # ...render the js that updates your view (for example, 
        # find and replace calling link with an "unselect" one )
      else
        # .. an error has occurred, render an "unprocessable entity" status
      end
    end
    
    
    def clear_accepted_answer
      @question = Question.find( params[:id] )
    
      # ...cue some logic to ensure current_user 
      # has required rights to update the question
    
      if @question.update_attributes( accepted_answer_id: nil )
        # ...render the js that updates your view (for example, 
        # find and replace calling link with a "select" one )
      else
        # .. an error has occurred, render an "unprocessable entity" status
      end
    end    
    
  • in your view

      <% if current_user?(answer.question.user) %>
        <% if answer.accepted?  %>
          <%= link_to "unselect", 
                      clear_accepted_answer_question_path( answer.question ),
                      method: :put,
                      remote: true %>
        <% else %>
          <%= link_to "select", 
                      accept_answer_question_path( 
                        answer.question, 
                        answer_id: answer.id 
                      ), 
                      method: :put,
                      remote: true %>
        <% end %>
      <% end %>
    

Upvotes: 1

Related Questions