Search code examples
javascriptruby-on-railsajaxvariablesdraper

Trouble implementing ajax. if statement wont work


I'm not sure what the problem is here to know what exactly I'm asking but I'll give it my best shot. I tried to bold where I think the issues is to make it clearer.

I'm working with ajax for the first time. In the app a user(1) searches for user(2), upon finding user(2) user(1) can click a button 'add relationship' which sends a relationship request to user(2). the button 'add relationship' should change to 'relationship requested' straight away and then upon refreshing the page, the button should change to 'edit relationship'.

'add relationship' = no relationship exists yet 'relationship requested' = pending relationship exists, acts like a success flash message 'edit relationship' = pending relationship exists or accepted relationship exists

It's all working except the last part where the button should change to 'edit relationship' upon refreshing the browser page. 'add relationship' appears instead

Any ideas this 'edit relationship' button wont appear? I am using state machine, draper and js-routes gems.

view/users/index:

where upon searching for user(2) their name appears and the button to 'add relationship', 'edit relationship' or 'relationship requested' buttons appear next to the name

the if statement here is what's not working I think. Why isn't it finding the relationship based on if it is pending or accepted?

<% if logged_in? %>
    <ul>
        <% @users.each do |user| %>
            <li>
                <%= user.name %>
                <div id="relationship-status">
                    <% if current_user.following.include?(user.id) || current_user.pending_following.include?(user.id) %>
                        <%= link_to "Edit Relationship", edit_relationship_path(followed_id: user.id), class: "btn btn-primary" %>
                    <% else %>
                        <%= link_to "Add Relationship", new_relationship_path(followed_id: user.id), class: "btn btn-primary", id: 'add-relationship', data: { followed_id: user.id.to_param } %>
                    <% end %>
                </div>
            </li>
        <% end %>
    </ul>
<% end %>   

controller/users:

  def index
    @users = User.search(params[:search])
  end

relationship.js:

$(document).ready(function() {

    $('#add-relationship').click(function(event) {
        event.preventDefault();
        var addRelationshipBtn = $(this);
        $.ajax({
            url: Routes.relationships_path({relationship: { followed_id: addRelationshipBtn.data('followedId') }}),
            dataType: 'json', 
            type: 'POST', 
            success: function(e) {
                addRelationshipBtn.hide();
                $('#relationship-status').html("<a href='#' class='btn btn-success'>Relationship Requested</a>");
            }
        });
    });
});

model/user:

class User < ActiveRecord::Base
  has_one :profile, dependent: :destroy
  has_many :pending_relationships,  class_name:  "Relationship",
                                    foreign_key: "follower_id"
  has_many :active_relationships,   class_name:  "Relationship",
                                    foreign_key: "follower_id",
                                    dependent:   :destroy
  has_many :passive_relationships,  class_name:  "Relationship",
                                    foreign_key: "followed_id",
                                    dependent:   :destroy                                
  has_many :following, -> { where(relationships: { state: "accepted" } ) }, through: :active_relationships,  source: :followed
  has_many :followers, through: :passive_relationships, source: :follower                              
  has_many :pending_following, -> { where(relationships: { state: "pending" } ) }, through: :pending_relationships,  source: :followed

relationship/decorator:

class RelationshipDecorator < Draper::Decorator
  delegate_all

  decorates :relationship

  def relationship_state
    model.state.titleize
  end

  def sub_message
    case model.state
    when 'pending'
      "Relationship request pending"
    when 'accepted'
      "You are now connected with #{model.followed.name}"
    end
  end
end

EDIT:

db/migrate:

class AddStateToRelationships < ActiveRecord::Migration
  def change
    add_column :relationships, :state, :string
    add_index :relationships, :state
  end
end

model/relationship:

class Relationship < ActiveRecord::Base
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
  validates :follower_id, presence: true
  validates :followed_id, presence: true

  after_destroy :delete_mutual_relationship!

  state_machine :state, initial: :pending do
    after_transition on: :accept, do: [:send_acceptance_email, :accept_mutual_relationship!]

    state :requested

    event :accept do
      transition any => :accepted
    end
  end

terminal output:

Started POST "/relationships?relationship%5Bfollowed_id%5D=25" for 127.0.0.1 at 2014-11-06 16:35:26 +1100
Processing by RelationshipsController#create as JSON
  Parameters: {"relationship"=>{"followed_id"=>"25"}}
  User Load (0.4ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 1 LIMIT 1
  User Load (0.3ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 25]]
   (0.1ms)  begin transaction
  SQL (5.4ms)  INSERT INTO "relationships" ("created_at", "followed_id", "follower_id", "state", "updated_at") VALUES (?, ?, ?, ?, ?)  [["created_at", "2014-11-06 05:35:26.360104"], ["followed_id", 25], ["follower_id", 1], ["state", "pending"], ["updated_at", "2014-11-06 05:35:26.360104"]]
  SQL (0.2ms)  INSERT INTO "relationships" ("created_at", "followed_id", "follower_id", "state", "updated_at") VALUES (?, ?, ?, ?, ?)  [["created_at", "2014-11-06 05:35:26.368921"], ["followed_id", 1], ["follower_id", 25], ["state", "requested"], ["updated_at", "2014-11-06 05:35:26.368921"]]
  Relationship Load (0.1ms)  SELECT  "relationships".* FROM "relationships"  WHERE "relationships"."id" = ? LIMIT 1  [["id", 49]]
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 1]]
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 25]]
  Rendered user_mailer/relationship_requested.html.erb (0.2ms)

UserMailer#relationship_requested: processed outbound mail in 27.9ms

Sent mail to example-24@example.com (14.2ms)
Date: Thu, 06 Nov 2014 16:35:26 +1100
From: noreply@example.com
To: example-24@example.com
Message-ID: <545b089e6206e_6c313ff72cf9cf78434189f@example.local.mail>
Subject: Firstname Surname wants to follow you. Please log in to accept
 this request
Mime-Version: 1.0
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

Hi example-24, 

Firstname Surname wants to follow you.

   (7.7ms)  commit transaction
Completed 200 OK in 71ms (Views: 0.3ms | ActiveRecord: 14.4ms)

what happens when I (user 1) search for a user example-24 (user id = 25) and then press the 'add relationship' button (as shown in sqlitebrowser: (see the bottom of the image for the 2 rows relevant to this example)

db relationship table

EDIT:

users/controller:

  def create
    if params[:relationship] && params[:relationship].has_key?(:followed_id)
      @followed = User.find(params[:relationship][:followed_id])
      # @followed = User.where(name: params[:relationship][:followed_id]).first
      @relationship = Relationship.request(current_user, @followed)
      respond_to do |format|
        if @relationship.new_record?
          format.html do
            flash[:danger] = "There was a problem creating that relationship request"
            redirect_to followed_path(@followed)
          end
          format.json { render json: @relationship.to_json, status: :precondition_failed }
        else
          format.html do
            flash[:success] = "Friend request sent"
            redirect_to followed_path(@followed)
          end
          format.json { render json: @relationship.to_json }
        end
      end
    else
      flash[:danger] = "Friend Required"
      redirect_to users_path
    end
  end

Solution

  • Although I can't fix this error of yours, You can do it like this.

    1. Have a form_for or link_to with an option remote true.
    2. A controller and a route to respond to your action

    For example:

    In your routes.rb

    resources :relationships, only: [:create, :destroy]
    

    And in your relationships_controller.rb

    def create
      //find the user_id of the to_be_followed user
      // like User.find(params[:relationship][:user_id] 
      // this :relationship comes from your controller
      // and current_user.relationsihps.create(followed_id: other_user_id)
      respond_to do |format|
        format.html { redirect_to user_path(other_user) }
        format.js
      end
    end
    
    //similarly
    def destroy
      //find the other_user, if relationship exists, destroy it
    end
    

    And then in your html

    __ _follow.html.erb

    <%= form_for(:relationship, url: relationships_path, remote: true) do |f| %>
      <div><%= f.hidden_field :followed_id, value: @user.id %></div>
      <%= f.submit "Follow", class: "btn btn-large btn-primary" %>
    <% end %>
    

    similarly a partial _unfollow.html.erb with the value of the submit button changed to Unfollow.

    (Although I am not a big fan of hidden fields. You could set up nested routes too.)

    So now you can have a method in your model say is_following(user) , which would check if the current_user and the `other_user have a relationship. And say in the show page

    <% if current_user.is_following(other_user) %>
      <%= render 'unfollow' %>
    <% else %>
      <%= render 'follow' %>
    <% end %>
    

    And then you need your js.erb files to handle the JavaScript response for you. In this case name create.js.erb and edit.js.erb for example:

    $("some_selector_name).html("<%= j render('shared/follow') %>")
    

    Regarding the state machine (which i feel is kindof overkill for a two state relation) can be used in your users model maybe like

    state_machine :state, initial: :unfollow
      event :confirm_follow do
        transition to: :follow, from: :unfollow
      end
      state :follow do
        def is_following?(user) 
          // do some checks
          //!!relationships.find(user.id)
        end
      end
    end