9

I'm making a GET call to an external source using the gem 'httparty'; here's my Controller.rb:

def show
  response = HTTParty.get('URI')
  user = JSON.parse(response)
  user.each {|line| puts line['user']['id']}
  #the "['user']['id']" is because of the nested JSON object that is returned after the 
  parse.
end

This returns the correct output in my rails console, but now the question is how do I save the ['id'] to my db?

Currently, my User model has :id and :name; the JSON object from the external API sends :id and :name along with a bunch of other information I don't need.

class User < ActiveRecord::Base
   attr_accessor :id, :name
end

Any help is appreciated, thanks!

asing
  • 385
  • 2
  • 7
  • 16
  • 1
    May be you should save your json `id` not in the `id` column but in smth like `uri_id`? In this case just create new column and use it in your application. I think it will be the best variant. – tiktak Jan 17 '13 at 08:25
  • Duly noted. I will try that; is there any way to loop through the json array to save only if :uri_id and :name? – asing Jan 17 '13 at 08:28

2 Answers2

16

First, I would suggest that you create another column for the id (say external_id or something), rather than saving it in the actual id column of the User model (that column is very important in ActiveRecord and you really don't want to be setting it directly). You can validate uniqueness on that column to ensure that you don't import the same user data into multiple separate records in the db.

Once you have that column, create a class method in your User model (I've called mine save_data_from_api) to load the JSON data into the db:

class User < ActiveRecord::Base

  # optional, but probably a good idea
  validates :external_id, :uniqueness => true

  def self.save_data_from_api
    response = HTTParty.get('URI')
    user_data = JSON.parse(response)
    users = user_data.map do |line|
      u = User.new
      u.external_id = line['user']['id']
      # set name value however you want to do that
      u.save
      u
    end
    users.select(&:persisted?)
  end

end

What this does:

  • Map the JSON data to a block (user_data.map) which takes each JSON user and
    1. initializes a new user (User.new)
    2. assigns it the id JSON data (line['user']['id'])
    3. saves the new user (u.save), and
    4. return its it (the last u in the block).
  • Then, take the result (assigned to users) and select only those that actually exist (were persisted) in the db (users.select(&:persisted?)). For example, if you have a uniqueness constraint on external_id and you try to load the db data again, u.save will return false, the record will not be (re-)created, and those results will be filtered out of the results returned from the method.

This may or may not be the return value you want. Also, you probably want to add more attribute assignments (name, etc. from the JSON data) in the block. I leave it up to you to fill in those other details.

Chris Salzberg
  • 27,099
  • 4
  • 75
  • 82
  • Hey Shioyama, this looks the the solution I'm going for. Currently, when I put this code into my project and open up my rails console, I get A HUGE JSON string when I evaluate User.save_data_from_api. Then, when I evaluate User.all I still get empty "external_id" and "name" = nil values. Ideas? – asing Jan 17 '13 at 09:14
  • You get nil `external_id` and `name` values because the code above does not save those attributes on the model, it only saves the `id` field. If you want to save the `id` data to an `external_id` field, you will need to add that attribute to your model, and change the `u.id = line['user']['id']` line to `u.external_id = line['user']['id']`. Also, the return value is not really important, but if it bothers you you could change the code to return the set of models, for example. – Chris Salzberg Jan 17 '13 at 09:45
  • I've elaborated on my answer, please see above. – Chris Salzberg Jan 17 '13 at 12:37
  • Thanks so much for the help @shioyama...I'm a beginner and having thorough descriptions (like you did above), really helps. Another quick question though, once I include this method in my Model, where is the best place to invoke it? – asing Jan 17 '13 at 21:18
  • That's a good question. It depends on the nature of the data: is this a one-time thing, or do you need to periodically load the user data from the API? Or do you want the db to basically always be in sync with the API data? – Chris Salzberg Jan 17 '13 at 22:33
  • It's static data, so one-time only. I'm still getting nil values when implementing the above code. I've already added the attributes to the model and migration files: https://gist.github.com/bba613569b946e390d5e – asing Jan 17 '13 at 22:44
  • You're getting nil values for `external_id`? I've tested the code and it works. It looks like you have no `external_id` attribute in your model (it's not in your migration). Also, why do you have a `user_id` attribute in your `User` migration? You shouldn't need that unless your users are associated with each other. – Chris Salzberg Jan 17 '13 at 23:39
  • I was actually testing the the :name and not :external_id. I will add external_id once I can successfully fill in the name attribute (as it's included in both my model and migration). For testing sake, I've actually put the code into my show method in the controller (didn't know where to invoke save_data_from_api), and rendered the code to the show.html.erb view by <%=@user.inspect%> (which is currently displaying nil). – asing Jan 17 '13 at 23:48
  • Sorry, but I really can't help you much more that this. One last thing I'd suggest would be to change `u.save` to `u.save!`, which will raise an exception if the save is unsuccessful, with the reason why it didn't save (assuming that's the problem). – Chris Salzberg Jan 17 '13 at 23:53
  • I really appreciate all the help, sorry for bombarding you with questions. You've provided me with more than enough. – asing Jan 17 '13 at 23:56
  • 1
    Glad to have helped! One thing I'd suggest when testing your models is, instead of putting code in a controller action and then accessing that action through the browser, just open a rails console (`rails c` from the command line) and try things out there. If something fails you'll get immediate feedback which makes it easy to debug. – Chris Salzberg Jan 19 '13 at 11:00
0

Here I am assuming that you have created the uri_id field to store the external id and the name field to store the external name in the user table.

Your Controller code -

def show
  response = HTTParty.get('URI')
  JSON.parse(response).each do |item|
    if (item.id != nil && item.name != nil)
       u = User.new(:uri_id => item.id, :name => item.name)
       u.save
    end
  end
end
sjain
  • 23,126
  • 28
  • 107
  • 185
  • Thanks for the answer Saurabh, but I'm not using devise. Just populating static data from an external API to my DB. – asing Jan 17 '13 at 09:23
  • than your task becomes more simple as now you only take the external params and store them to user table in db. See my updated answer of how to loop through json and save the external params to db only if :uri_id and :name. – sjain Jan 17 '13 at 09:30
  • -1 This doesn't work. You don't seem to have read the OPs explanation: the JSON data is nested (for one thing) and also `item.id`, `item.name`, etc. is not how you would access the parsed data anyway. `JSON.parse(response)` is an array of hash objects, so you need to access the elements with `item['id']`, `item['name']`, etc. – Chris Salzberg Jan 17 '13 at 12:44