0

I have a Rails model called Tasks, which has just one attribute: resource_id. This refers to the ID of a Resource (another model) on another Rails website running on a different URL with a different database.

When you request a Resource (e.g. http://localhost:3020/resources/23.json), it returns the resource in JSON format, for example:

{
  "title": "Hello",
  "description": "Lorem ipsum..."
}

I want to be able to refer to the requested resource from my current Rails website (the one with the Task) as such:

# returns a Task with `resource_id: 23`
@task = Task.find(1)

# refers to the Resource from the other Rails server
@task.resource.title

How can I make Rails get the Resource at the same time it finds the Task from the local database?

I tried making a resource attribute in task.rb which requests the Resource when it's called as such:

require 'net/http'

class Task < ApplicationRecord
  def resource
    res = Net::HTTP.get(URI.parse('http://localhost:3020/resources/' + resource_id.to_s + '.json'))
    JSON.parse(res)
  end
end

However, this runs every time I call task.resource which is very slow. Can I just request the Resource once and store it locally in some sort of pseudo-attribute (I don't know what it's called)?

Pal Kerecsenyi
  • 560
  • 4
  • 18

3 Answers3

1

Query all content of external database table not is a efficient. But you can add a caching for your controller:

for example, you can use caching with redis:

require 'net/http'

class Task < ApplicationRecord
  def resource
    res = $redis.fetch(:resource, expires_in: 1.hour) do
      Net::HTTP.get(URI.parse('http://localhost:3020/resources/' + resource_id.to_s + '.json'))
      JSON.parse(res)
    end
  end
end
Sergio Belevskij
  • 2,478
  • 25
  • 24
0

Here's how I solved my own question:

Firstly, add a string attribute to Task called resource_string.

task.rb

require 'net/http'

class Task < ApplicationRecord
  def resource
    JSON.parse(resource_string)
  end

  after_find do |task|
    task.resource_string = request_resource task
  end

  private

  def request_resource(task)
    Net::HTTP.get(URI.parse('http://localhost:3020/resources/' + task.resource_id.to_s + '.json'))
  end
end

You can then access the resource as follows:

@task = Task.find(1)
@task.resource['title']

It will only make the request once and write it to the resource_string attribute after the find method is called.

Pal Kerecsenyi
  • 560
  • 4
  • 18
-1

You need to add an association to Tasks and Resources:

in task.rb:

  has_many: :resources

in resource.rb:

  belongs_to: :task

These associations automatically add methods like your resource method to the models: https://guides.rubyonrails.org/association_basics.html

Then in your controller action, use :includes to join the two tables:

@tasks = Task.includes(:resource)

See here: https://apidock.com/rails/ActiveRecord/QueryMethods/includes

and here for an explanation: https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

Will Wilson
  • 335
  • 2
  • 9
  • Sorry, maybe I didn't clarify. `Task` is on a different Rails server to `Resource`, so the server with `Task` on it needs to make a request to the `Resource` server via `Net::HTTP` – Pal Kerecsenyi Mar 09 '19 at 17:48
  • If each task has a distinct resource, I don't see how you can avoid calling the remote resource each time you need it, without pulling the resources into your database. Probably the simplest answer would be to create a table or an attribute on the tasks table and write the resource to that each time you call for one. that way you'll only ever have to call remotely for the resource once, then you can use regular ActiveRecord as in my answer. – Will Wilson Mar 09 '19 at 18:00
  • I suppose that would be a good idea, I'll add it as an answer. Thanks for the suggestion! – Pal Kerecsenyi Mar 09 '19 at 18:02