Search code examples
ruby-on-railsrubyforeign-keysmany-to-manypaperclip

Showing random Product images from same category as the selected product is in. ROR app


I'm modifying a Rails app I've been working on.

I installed a model images.rb to handle all images for products.

When the user has selected a product the user sees the product at app/views/products/show.html.erb

And at the bottom of the show.html.erb the app gives the user suggestions for similar product in the same category as the product the user is looking at.

Before I added the image.rbmodel to handle the images each product had one picture attached to it and every thing worked, but now I'm getting an error. Below is the code I used to display the random products in the same category:

the show.html.erb

 <div class="row product-teaser">
  <h4 class="text-center teaser-text"> similar products to <%= @product.title %> : </h4>
   <% @products_rand.each do |product| %>
      <div class="col-sm-2 col-xs-3 center-block product-thumbs-product-view" >
            <%= link_to product_path (product) do %>
                <%= image_tag @product.image.url, :size => "100%x100%", class: "img-responsive center-block" %>
            <% end %>  
          <h5 class="text-center"><%= link_to product.title, product, class: "text-center" %></h5>    
      </div>
    <% end %>   
  </div>

And in the products_controller.rb show method I had this variable to fetch the random products from the same category.

class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]

  def show    
     @products_rand = Product.where(category_id: @product.category_id).order("RANDOM()").limit(6)
  end

private
   # Use callbacks to share common setup or constraints between actions.
  def set_product
    @product = Product.find(params[:id])
  end

  # Never trust parameters from the scary internet, only allow the white list through.
  def product_params
    params.require(:product).permit(:title, :description, :price_usd,  :image, :category_id, :stock_quantity, :label_id, :query, :slug, images_attributes: [:image , :id , :_destroy])
  end
 end

After I added the image.rb I always get this error: undefined method 'url' for "":String when trying to load the show page. The error is appearing in this line: <%= image_tag @product.image.url, :size => "100%x100%", class: "img-responsive center-block" %>

the product.rbmodel

class Product < ActiveRecord::Base
  acts_as_list :scope => [:category, :label]
  belongs_to :category
  belongs_to :label

  has_many :images
  accepts_nested_attributes_for :images
  has_many :product_items, :dependent => :destroy

    validates :title, :description, presence: true
    validates :price_usd, :price_isl, numericality: {greater_than_or_equal_to: 0.01}
    validates :title, uniqueness: true  
 end

the image.rbmodel

class Image < ActiveRecord::Base
  belongs_to :product
    has_attached_file :image, styles: { medium: "500x500#", thumb: "100x100#" }
    validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
 end

I'm not sure what to do, can someone advise me?


Solution

  • If your Product has_many :images then @product.image.url is not going to work.

    @product.images.sample will give you a random image for that product.

    It looks like you're using Paperclip, which you have configured to add an image attachment to your Image model, so you need to add an extra method to your chain:

    @product.images.sample.image.url will retrieve the URL for the image attachment of a randomly selected image association.

    @product.image is currently returning an empty string, so you must have an image column on your products table of type string or text, or an image method on your model. If you don't need it anymore then you should remove it.

    If it's a column in your table, in a migration file:

    def change
      remove_column :products, :image
    end
    

    Edit

    As suggested by MrYoshiji, getting a random image would be more efficiently achieved at the database level, especially if a product has a lot of associated images. A scope method on the Image model would do the trick, along with a Product method to abstract it and simplify your view.

    class Image < ActiveRecord::Base
      scope :random, -> { order("RANDOM()").limit(1) }
    end
    
    class Product < ActiveRecord::Base
      def random_image
        images.random.take
      end
    end
    

    Then in your view

    @product.random_image.image.url