Search code examples
ruby-on-railsjsonactive-model-serializers

Can't access ActiveModel::Serialized attribute from views


Trying to fetch some products from this API - but I can't seem to access my ActiveModel::Serialized attribute product.name from my views. Where did I go wrong?

Model:

require 'rest_client'

class ThirdPartyProducts
    include ActiveModel::Serializers::JSON

    # Not setting attributes here as there's just too many in the JSON response
    # attr_accessor :lorem, :ipsum, :dolor, :sit, :amet

    def attributes=(hash)
      hash.each do |key, value|

        # Set attributes here instead
        # http://stackoverflow.com/questions/1734984/why-cant-i-use-attr-accessor-inside-initialize

        self.class.send(:attr_accessor, "#{key}")
        send("#{key}=", value)
      end
    end

    def self.fetch
      response = RestClient.get "http://api.shopstyle.com/api/v2/products?pid=uid7849-6112293-28&format=json&fts=women"
      raw_products = JSON.parse(response)["products"]

      raw_products.map do |product|
        what_to_name_this ||= self.new
        product = what_to_name_this.from_json(product.to_json)

        name = product.name
        # url = ...
        # image = ...
      end
    end
end

Controller:

class MainController < ApplicationController
  def index
    @products = ThirdPartyProducts.fetch
  end
end

View:

<% if @products.any? %>
  <% @products.each do |product| %>
    <div class="product">
      <%= product.name %>
    </div>
  <% end %>
<% end %>

Error:

NoMethodError in Main#index
Showing /root/rails-repo/app/views/main/index.html.erb where line #5 raised:

undefined method `name' for "McQ Alexander McQueen Mesh-paneled stretch-jersey top":String
Extracted source (around line #5):
  <% if @products.any? %>
    <% @products.each do |product| %>
      <div class="product">
        <%= product.name %>
      </div>
    <% end %>
  <% end %> 

Rails.root: /root/rails-repo

app/views/main/index.html.erb:5:in `block in _app_views_main_index_html_erb__1422808888741532498_21926060'
app/views/main/index.html.erb:3:in `each'
app/views/main/index.html.erb:3:in `_app_views_main_index_html_erb__1422808888741532498_21926060'

Solution

  • Assuming third party products to be an array of products you might want to redefine your class in something like:

    class ThirdPartyProducts
      require 'rest_client'
    
      def self.fetch
        response = RestClient.get "http://api.shopstyle.com/api/v2/products?pid=uid7849-6112293-28&format=json&fts=women"
        raw_products = JSON.parse(response)["products"]
        products = []
        raw_products.map { |pro| Product.new(pro) }
      end
    end
    

    And:

    class Product
       def new(args)
         hash.each do |key, value| 
           self.class.send(:attr_accessor, "#{key}") }
           send("#{key}=", value)
         end
       end
    end
    

    With this the line:

    @products = ThirdPartyProducts.fetch
    

    Is returning an array of Product instances each with the response of the api.

    As the array exists you don't need anymore:

    <% if @products.any? %>
    

    @products.any is always and array, and if empty the iteration will not render the next lines so you don't have any logic in the view.

    Updated answer:

    Other option is using a hash in the view for each element.

    @products = Products.fetch
    
    class Products
      def self.fetch
        response = RestClient.get "http://api.shopstyle.com/api/v2/products?pid=uid7849-6112293-28&format=json&fts=women"
        JSON.parse(response)["products"]
      end
    end
    

    And in the views:

    <% @products.each do |product| %>
       <%= product["name"] %>
       <%= product["price"] %>
    <% end %>