Mark Boulder Mark Boulder - 29 days ago 10
JSON Question

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'

Answer

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 %>
Comments