whichdigital / active-rest-client

ActiveRestClient API Client
https://rubygems.org/gems/active_rest_client
MIT License
385 stars 44 forks source link

Multiple clients for same API #107

Closed mustela closed 8 years ago

mustela commented 8 years ago

Following this issue, I'm looking for a way to have the same model but with different credentials/header.

class Person < ActiveRestClient::Base
  base_url "https://myapi.com/resource/:dynamic-id"

  before_request do |name, request|
     request.url.gsub!("#dynamic-id", Configuration.dynamic_id)
  end

  get :all, "/something"
end

This is just to demonstrate the expected behavior the

Configuration.dynamic_id = 1234
Person.all
result: {id:1, name: 'Paul'}
Configuration.dynamic_id = 456
Person.all
result: {id:2, name: 'Peter'}

It would be solved if the concept of a client would exist in the lib, so I could do something like client.person.all and keep the configuration in the client. But haven't found a way to do it yet...

Any idea how could I achieve this approach?

Thanks a lot!

andyjeffries commented 8 years ago

The approach above looks like it would work, is there an error?

mustela commented 8 years ago

Yes, the error (one of it) is that you can't use it with several calls like

Configuration.dynamic_id = 1234
Person.all
Configuration.dynamic_id = 456
Person.all

Person.find(888) 

The last line would use the second configuration, but how do I do if I want to use the first one?

andyjeffries commented 8 years ago

OK, I get you. Unfortunately, it's really not built around the concept of having a client object.

I can't think of an easy way around this either. What is this used for? If it's literally "the logged in user", then it wouldn't be changing within a request. Can you give me a real world use case so I can try to understand the need a bit better and maybe help with an idea.

mustela commented 8 years ago

Sure, actually using the filter maybe isn't the best example, because as you are saying, it won't change per request.

I'm working on a rb client for this api, as you can see all of the resources started with /merchants/{id}/[something] And for authentication you have to send a key with a token.

I have several merchants and I need to be able to use the same model structure for all of them, at the same time. Like I explained above.

Make sense? I love this library so hope we can find a workaround for it.

andyjeffries commented 8 years ago

OK, so the easiest solution is probably something like:

class Person < ActiveRestClient::Base
  base_url "https://myapi.com/resource/:merchant_id"

  get :all, "/something"
end

Person.all(merchant_id: 1234)
Person.all(merchant_id: 555)

So you pass the merchant_id through on each call. It seems strange that you're going to be changing the merchant ID mid-request, but that's probably the best way - be explicit.

Failing that, you could create a wrapper client class.

class MerchantizedApi
  def initialize(merchant_id)
    @merchant_id = merchant_id
  end

  def method_missing(name, *args)
    # Split name in to class and method
    # Find the last args that is a hash (or add one if there isn't)
    # set the :merchant_id field in that hash to @merchant_id
    # Use class_name.constantize.send(method, args)
  end
end

@merchant = MerchantizedApi.new(1234)
@merchant.person_find

It's a bit icky, but something like that may work.

mustela commented 8 years ago

First of all, thanks for spending the time to help me ;), and yes, your second approach could work, since I don't want to pass around the merchant id all over the code.

Also I was checking something like the following could work too:


class Client

  def initialize(merchant_id)
    build_client merchant_id
  end

  def build_client(merchant_id)
    model = Class.new(Person) do
              base_url "https://myapi.com/resource/#{merchant_id}"
            end
    self.class.send(:define_method, :person, -> { model })
  end
end

So I could do

client = Client.new(1234)
client.person.all

client2 = Client.new(456)
client2.person.all
andyjeffries commented 8 years ago

It's a great plan, but even better may be something like this (so you can pass in a list of classes you want to wrap):

class Client
  def initialize(merchant_id, *classes)
    build_client merchant_id, classes
  end

  def build_client(merchant_id, classes)
    classes.each do |klass|
      model = Class.new(klass) do
        base_url "https://myapi.com/resource/#{merchant_id}"
      end
      self.class.send(:define_method, klass.name.underscore, -> { model })
    end
  end
end
client = Client.new(1234, Person, Task, Project)
client.person.find(1)
client.task.all
mustela commented 8 years ago

Yeah, def, was just showing you my point, but yes. I just did that but instead I used a constant to keep all my clases since they won't change.

Thanks a lot!!