Richlewis
Richlewis

Reputation: 15374

Access Instance Variable through Ajax Requests and Partials

Within my application I can add an object to my CartItem model but am getting undefined method 'id' for nil:NilClass, when destroying the record and rendering my view via Ajax.

So upon page load /images/7 for example I have

<div id="cart_form">
  <% if @cart_item_collection.include?(@image.id) %>
    <%= render 'shared/forms/remove_from_cart', locals: { image: @image } %>
  <% else %>
    <%= render 'shared/forms/add_to_cart', locals: { image: @image } %>
  <% end %>
</div>

class ImagesController < ApplicationController
  def show
    @image = Image.find(params[:id])
    @cart_item_collection = CartItem.where(user_id: current_or_guest_user).pluck(:image_id)
  end
end

_add_to_cart.html.erb

<%= form_for(@cart_item, url: cart_item_path, :remote => true do |f| %>
   <%= f.hidden_field :image_id, value: @image.id %>
   <%= f.submit "Add To Cart" %>
<% end %>

_remove_from_cart.html.erb

<%=button_to cart_item_path(id: @cart_item), method: :delete, :remote => true, class: "icon di_darckblue extra-large button fill", name: 'images' do %>
<i class="di-shopping-cart-up"></i>
Remove From Cart

As previously mentioned I can create a CartItem fine, I render this js.erb file

$("#cart_form").html("<%= j render partial: 'shared/forms/remove_from_cart', locals: { image: @image } %>");

But when I try to destroy the CartItem thats when I get the error

$("#cart_form").html('<%= j render partial: "shared/forms/add_to_cart", locals: { image: @image } %>');

How can I keep @image persisted throughout each call?

Thanks

Upvotes: 1

Views: 418

Answers (1)

Sahil
Sahil

Reputation: 3358

We need to pass image id when making AJAX calls so that the @image object can be obtained in the views when partials are getting rendered on AJAX completion.

Therefore we fetch the @image using the image_id parameter.

show.html.erb

<div id="cart_form">
  <% if @cart_item_collection.include?(@image.id) %>
    <%= render 'shared/forms/remove_from_cart', locals: { image: @image } %>
  <% else %>
    <%= render 'shared/forms/add_to_cart', locals: { image: @image } %>
  <% end %>
  <p class="error"></p>
</div>

images_controller.rb

class ImagesController < ApplicationController
  def show
    @image = Image.find(params[:id])
    @cart_item_collection = CartItem.where(user_id: current_or_guest_user).pluck(:image_id)
  end
end

_add_to_cart.html.erb

<%= form_for(@cart_item, url: cart_item_path, :remote => true do |f| %>
   <%= f.hidden_field :image_id, value: @image.id %>
   <%= f.submit "Add To Cart" %>
<% end %>

_remove_from_cart.html.erb

<%=button_to cart_item_path(id: @cart_item, image_id: @image.id), method: :delete, :remote => true, class: "icon di_darckblue extra-large button fill", name: 'images' do %>
<i class="di-shopping-cart-up"></i>
Remove From Cart

Adding to cart,

def add_to_cart
    @image = Image.find(params[:cart_item][:image_id])
    #if @image is present
      #your code
    #else
       #@error= "Item could not be added to cart."
    #end
end

create.js.erb

<% if !@error %>
    $("#cart_form").html("<%= j render partial: 'shared/forms/remove_from_cart', locals: { image: @image } %>");
    $('.error').html('');
<% else %>
    $('.error').html(<%= @error %>);
<% end %>

Removing from cart,

def destroy
    @image = Image.find(params[:image_id])
    #if @image
      #destroying @cart_item
      #rendering destroy.js.erb
      #@cart_item = CartItem.new
    #else
      @error="Item could not be removed from cart"
    #end 
end

destroy.js.erb:

<% if !@error %>
    $("#cart_form").html('<%= j render partial: "shared/forms/add_to_cart", locals: { image: @image } %>');
    $('.error').html('');
<% else %>
    $('.error').html(<%= @error %>);
<% end %>

Another way: As you are just using image id inside the partials: here we are just passing the image_id in controller and sending it back to front end.

But the only check that we would have to do is checking if the image exists for the image_id passed to controller, as there are chances that user can change the value in front end.

To check that we would have to check for presence of image on every AJAX call made, which makes the first approach more safer. But if that doesn't matter to you then you can go with this approach.

<div id="cart_form">
  <% if @cart_item_collection.include?(@image.id) %>
    <%= render 'shared/forms/remove_from_cart', locals: { image_id: @image.id } %>
  <% else %>
    <%= render 'shared/forms/add_to_cart', locals: { image_id: @image.id } %>
  <% end %>
</div>

class ImagesController < ApplicationController
  def show
    @image = Image.find(params[:id])
    @cart_item_collection = CartItem.where(user_id: current_or_guest_user).pluck(:image_id)
  end
end

_add_to_cart.html.erb

<%= form_for(@cart_item, url: cart_item_path, :remote => true do |f| %>
   <%= f.hidden_field :image_id, value: image_id %>
   <%= f.submit "Add To Cart" %>
<% end %>

_remove_from_cart.html.erb

<%=button_to cart_item_path(id: @cart_item, image_id: image_id), method: :delete, :remote => true, class: "icon di_darckblue extra-large button fill", name: 'images' do %>
<i class="di-shopping-cart-up"></i>
Remove From Cart

Adding to cart, I render this js.erb file,

def add_to_cart
    @image_id = params[:cart_item][:image_id]
    #your code

end

create.js.erb

$("#cart_form").html("<%= j render partial: 'shared/forms/remove_from_cart', locals: { image_id: @image_id } %>");

Removing from cart,

def destroy
    @image = params[:image_id]
    #destroying @cart_item
    #rendering destroy.js.erb
end

destroy.js.erb:

$("#cart_form").html('<%= j render partial: "shared/forms/add_to_cart", locals: { image_id: @image_id } %>');

Upvotes: 2

Related Questions