Git Product home page Git Product logo

active-rest-client's Introduction

ActiveRestClient

Build Status Coverage Status Code Climate Gem Version Average time to resolve an issue Percentage of issues still open

This gem is for accessing REST services in an ActiveRecord style. ActiveResource already exists for this, but it doesn't work where the resource naming doesn't follow Rails conventions, it doesn't have in-built caching and it's not as flexible in general.

Deprecation Note for non-Which? Users

This gem isn't being updated here. If you want the functionality of ActiveRestClient you should probably look at this fork, which is being actively maintained by the original author:

(https://github.com/flexirest/flexirest/)

Some background (https://github.com/flexirest/flexirest/blob/master/Migrating-from-ActiveRestClient.md)

Installation

Add this line to your application's Gemfile:

gem 'active_rest_client'

And then execute:

$ bundle

Or install it yourself as:

$ gem install active_rest_client

Usage

First you need to create your new model class:

# config/environments/production.rb
MyApp::Application.configure do
  # ...
  config.api_server_url = "https://www.example.com/api/v1"
end

# app/models/person.rb
class Person < ActiveRestClient::Base
  base_url Rails.application.config.api_server_url

  get :all, "/people"
  get :find, "/people/:id"
  put :save, "/people/:id"
  post :create, "/people"
end

Note I've specified the base_url in the class above. This is useful where you want to be explicit or use different APIs for some classes and be explicit. If you have one server that's generally used, you can set it once with a simple line in a config/initializer/{something}.rb file:

ActiveRestClient::Base.base_url = "https://www.example.com/api/v1"

Any base_url settings in specific classes override this declared default. You can then use your new class like this:

# Create a new person
@person = Person.create(
  first_name:"John"
  last_name:"Smith"
)

# Find a person (not needed after creating)
id = @person.id
@person = Person.find(id)

# Update a person
@person.last_name = "Jones"
@person.save

# Get all people
@people = Person.all
@people.each do |person|
  puts "Hi " + person.first_name
end

If an API returns an array of results and you have will_paginate installed then you can call the paginate method to return a particular page of the results (note: this doesn't reduce the load on the server, but it can help with pagination if you have a cached response).

@people = Person.all
@people.paginate(page: 1, per_page: 10).each do |person|
  puts "You made the first page: " + person.first_name
end

Note, you can assign to any attribute, whether it exists or not before and read from any attribute (which will return nil if not found). If you pass a string or a number to a method it will assume that it's for the "id" field. Any other field values must be passed as a hash and you can't mix passing a string/number and a hash.

@person = Person.find(1234)  # valid
@person = Person.find("1234")  # valid
@person = Person.find(:id => 1234)  # valid
@person = Person.find(:id => 1234, :name => "Billy")  # valid
@person = Person.find(1234, :name => "Billy")  # invalid

You can also call any mapped method as an instance variable which will pass the current attribute set in as parameters (either GET or POST depending on the mapped method type). If the method returns a single instance it will assign the attributes of the calling object and return itself. If the method returns a list of instances, it will only return the list. So, we could rewrite the create call above as:

@person = Person.new
@person.first_name = "John"
@person.last_name  = "Smith"
@person.create
puts @person.id

The response of the #create call set the attributes at that point (any manually set attributes before that point are removed).

If you have attributes beginning with a number, Ruby doesn't like this. So, you can use hash style notation to read/write the attributes:

@tv = Tv.find(model:"UE55U8000") # { "properties" : {"3d" : false} }
puts @tv.properties["3d"]
@tv.properties["3d"] = true

If you want to debug the response, using inspect on the response object may well be useful. However, if you want a simpler output, then you can call #to_json on the response object:

@person = Person.find(email:"[email protected]")
puts @person.to_json

Advanced Features

Faraday Configuration

ActiveRestClient uses Faraday to allow switching HTTP backends, the default is to just use Faraday's default. To change the used backend just set it in the class by setting adapter to a Faraday supported adapter symbol.

ActiveRestClient::Base.adapter = :net_http
# or ...
ActiveRestClient::Base.adapter = :patron

In versions before 1.2.0 the adapter was hardcoded to :patron, so if you want to ensure it still uses Patron, you should set this setting.

If you want more control you can pass a complete configuration block ("complete" means that the block does not override the default configuration, but rather replaces it). For available config variables look into the Faraday documentation.

ActiveRestClient::Base.faraday_config do |faraday|
  faraday.adapter(:net_http)
  faraday.options.timeout       = 10
  faraday.headers['User-Agent'] = "ActiveRestClient/#{ActiveRestClient::VERSION}"
end

Associations

There are two types of association. One assumes when you call a method you actually want it to call the method on a separate class (as that class has other methods that are useful). The other is lazy loading related classes from a separate URL.

Association Type 1 - Loading Other Classes

If the call would return a single instance or a list of instances that should be considered another object, you can also specify this when mapping the method using the :has_one or :has_many options respectively. It doesn't call anything on that object except for instantiate it, but it does let you have objects of a different class to the one you initially called.

class Expense < ActiveRestClient::Base
  def inc_vat
    ex_vat * 1.20
  end
end

class Address < ActiveRestClient::Base
  def full_string
    return "#{self.street}, #{self.city}, #{self.region}, #{self.country}"
  end
end

class Person < ActiveRestClient::Base
  get :find, "/people/:id", :has_many => {:expenses => Expense}, :has_one => {:address => Address}
end

@person = Person.find(1)
puts @person.expenses.reduce {|e| e.inc_vat}
puts @person.address.full_string

Association Type 2 - Lazy Loading From Other URLs

When mapping the method, passing a list of attributes will cause any requests for those attributes to mapped to the URLs given in their responses. The response for the attribute may be one of the following:

"attribute" : "URL"
"attribute" : ["URL", "URL"]
"attribute" : { "url" : "URL"}
"attribute" : { "href" : "URL"}
"attribute" : { "something" : "URL"}

The difference between the last 3 examples is that a key of url or href signifies it's a single object that is lazy loaded from the value specified. Any other keys assume that it's a nested set of URLs (like in the array situation, but accessible via the keys - e.g. object.attribute.something in the above example).

It is required that the URL is a complete URL including a protocol starting with "http". To configure this use code like:

class Person < ActiveRestClient::Base
  get :find, "/people/:id", :lazy => [:orders, :refunds]
end

And use it like this:

# Makes a call to /people/1
@person = Person.find(1)

# Makes a call to the first URL found in the "books":[...] array in the article response
# only makes the HTTP request when first used though
@person.books.first.name

Association Type 3 - HAL Auto-loaded Resources

You don't need to define lazy attributes if they are defined using HAL (with an optional embedded representation). If your resource has an _links item (and optionally an _embedded item) then it will automatically treat the linked resources (with the _embedded cache) as if they were defined using :lazy as per type 2 above.

If you need to, you can access properties of the HAL association. By default just using the HAL association gets the embedded resource (or requests the remote resource if not available in the _embedded list).

@person = Person.find(1)
@person.students[0]._hal_attributes("title")

Association Type 4 - Nested Resources

It's common to have resources that are logically children of other resources. For example, suppose that your API includes these endpoints:

HTTP Verb Path
POST /magazines/:magazine_id/ads create a new ad belonging to a magazine
GET /magazines/:magazine_id/ads display a list of all ads for a magazine

In these cases, your child class will contain the following:

class Ad < ActiveRestClient::Base
  post :create, "/magazines/:magazine_id/ads"
  get :all, "/magazines/:magazine_id/ads"
end

You can then access Ads by specifying their magazine IDs:

Add.all(magazine_id: 1)
Add.create(magazine_id: 1, title: "My Add Title")

Combined Example

OK, so let's say you have an API for getting articles. Each article has a property called title (which is a string) and a property images which includes a list of URIs. Following this URI would take you to a image API that returns the image's filename and filesize. We'll also assume this is a HAL compliant API. We would declare our two models (one for articles and one for images) like the following:

class Article < ActiveRestClient::Base
  get :find, '/articles/:id', has_many:{:images => Image} # ,lazy:[:images] isn't needed as we're using HAL
end

class Image < ActiveRestClient::Base
  # You may have mappings here

  def nice_size
    "#{size/1024}KB"
  end
end

We assume the /articles/:id call returns something like the following:

{
  "title": "Fly Fishing",
  "author": "J R Hartley",
  "images": [
    "http://api.example.com/images/1",
    "http://api.example.com/images/2"
  ]
}

We said above that the /images/:id call would return something like:

{
  "filename": "http://cdn.example.com/images/foo.jpg",
  "filesize": 123456
}

When it comes time to use it, you would do something like this:

@article = Article.find(1)
@article.images.is_a?(ActiveRestClient::LazyAssociationLoader)
@article.images.size == 2
@article.images.each do |image|
  puts image.inspect
end

At this point, only the HTTP call to '/articles/1' has been made. When you actually start using properties of the images list/image object then it makes a call to the URL given in the images list and you can use the properties as if it was a nested JSON object in the original response instead of just a URL:

@image = @article.images.first
puts @image.filename
# => http://cdn.example.com/images/foo.jpg
puts @image.filesize
# => 123456

You can also treat @image looks like an Image class (and you should 100% treat it as one) it's technically a lazy loading proxy. So, if you cache the views for your application should only make HTTP API requests when actually necessary.

puts @image.nice_size
# => 121KB

Caching

Expires and ETag based caching is enabled by default, but with a simple line in the application.rb/production.rb you can disable it:

ActiveRestClient::Base.perform_caching = false

or you can disable it per classes with:

class Person < ActiveRestClient::Base
  perform_caching false
end

If Rails is defined, it will default to using Rails.cache as the cache store, if not, you'll need to configure one with a ActiveSupport::Cache::Store compatible object using:

ActiveRestClient::Base.cache_store = Redis::Store.new("redis://localhost:6379/0/cache")

Using filters

You can use filters to alter get/post parameters, the URL or set the post body (doing so overrides normal parameter insertion in to the body) before a request or to adjust the response after a request. This can either be a block or a named method (like ActionController's before_filter/before_action methods).

The filter is passed the name of the method (e.g. :save) and an object (a request object for before_request and a response object for after_request). The request object has four public attributes post_params (a Hash of the POST parameters), get_params (a Hash of the GET parameters), headers and url (a String containing the full URL without GET parameters appended)

require 'secure_random'

class Person < ActiveRestClient::Base
  before_request do |name, request|
    if request.post? || name == :save
      id = request.post_params.delete(:id)
      request.get_params[:id] = id
    end
  end

  before_request :replace_token_in_url

  before_request :add_authentication_details

  before_request :replace_body

  before_request :override_default_content_type

  private

  def replace_token_in_url(name, request)
    request.url.gsub!("#token", SecureRandom.hex)
  end

  def add_authentication_details(name, request)
    request.headers["X-Custom-Authentication-Token"] = ENV["AUTH_TOKEN"]
  end

  def replace_body(name, request)
    if name == :create
      request.body = request.post_params.to_json
    end
  end

  def override_default_content_type(name, request)
    if name == :save
      request.headers["Content-Type"] = "application/json"
    end
  end
end

If you need to, you can create a custom parent class with a before_request filter and all children will inherit this filter.

class MyProject::Base < ActiveRestClient::Base
  before_request do |name, request|
    request.get_params[:api_key] = "1234567890-1234567890"
  end
end

class Person < MyProject::Base
  # No need to declare a before_request for :api_key, already defined by the parent
end

After filters work in exactly the same way:

class Person < ActiveRestClient::Base
  after_request :fix_empty_content

  private

  def fix_empty_content(name, response)
    if response.status == 204 && response.body.blank?
      response.body = '{"empty": true}'
    end
  end
end

Lazy Loading

ActiveRestClient supports lazy loading (delaying the actual API call until the response is actually used, so that views can be cached without still causing API calls).

Note: Currently this isn't enabled by default, but this is likely to change in the future to make lazy loading the default.

To enable it, simply call the lazy_load! method in your class definition:

class Article < ActiveRestClient::Base
  lazy_load!
end

If you have a ResultIterator that has multiple objects, each being lazy loaded or HAL linked resources that isn't loaded until it's used, you can actually parallelise the fetching of the items using code like this:

items.parallelise(:id)

# or

items.parallelise do |item|
  item.id
end

This will return an array of the named method for each object or the response from the block and will have loaded the objects in to the resource.

Authentication

Basic

You can authenticate with Basic authentication by putting the username and password in to the base_url or by setting them within the specific model:

class Person < ActiveRestClient::Base
  username 'api'
  password 'eb693ec-8252c-d6301-02fd0-d0fb7-c3485'

  # ...
end

Api-Auth

Using the Api-Auth integration it is very easy to sign requests. Include the Api-Auth gem in your Gemfile and in then add it to your application. Then simply configure Api-Auth one time in your app and all requests will be signed from then on.

require 'api-auth'

@access_id = '123456'
@secret_key = 'abcdef'
ActiveRestClient::Base.api_auth_credentials(@access_id, @secret_key)

You can also specify different credentials for different models just like configuring base_url

class Person < ActiveRestClient::Base
  api_auth_credentials('123456', 'abcdef')
end

For more information on how to generate an access id and secret key please read the Api-Auth documentation.

Body Types

By default ActiveRestClient puts the body in to normal CGI parameters in K=V&K2=V2 format. However, if you want to use JSON for your PUT/POST requests, you can use either (the other option, the default, is :form_encoded):

class Person < ActiveRestClient::Base
  request_body_type :json
  # ...
end

or

ActiveRestClient::Base.request_body_type = :json

This will also set the header Content-Type to application/x-www-form-urlencoded by default or application/json; charset=utf-8 when :json. You can override this using the filter before_request.

If you have an API that is inconsistent in its body type requirements, you can also specify it on the individual method mapping:

class Person < ActiveRestClient::Base
  request_body_type :form_encoded # This is the default, but just for demo purposes

  get :all, '/people', request_body_type: :json
end

Parallel Requests

Sometimes you know you will need to make a bunch of requests and you don't want to wait for one to finish to start the next. When using parallel requests there is the potential to finish many requests all at the same time taking only as long as the single longest request. To use parallel requests you will need to set Active-Rest-Client to use a Faraday adapter that supports parallel requests (such as Typhoeus).

# Set adapter to Typhoeus to use parallel requests
ActiveRestClient::Base.adapter = :typhoeus

Now you just need to get ahold of the connection that is going to make the requests by specifying the same host that the models will be using. When inside the in_parallel block call request methods as usual and access the results after the in_parallel block ends.

ActiveRestClient::ConnectionManager.in_parallel('https://www.example.com') do
    @person = Person.find(1234)
    @employers = Employer.all

    puts @person #=> nil
    puts @employers #=> nil
end # The requests are all fired in parallel during this end statement

puts @person.name #=> "John"
puts @employers.size #=> 7

Faking Calls

There are times when an API hasn't been developed yet, so you want to fake the API call response. To do this, you can simply pass a fake option when mapping the call containing the response.

class Person < ActiveRestClient::Base
  get :all, '/people', fake: [{first_name:"Johnny"}, {first_name:"Bob"}]
end

You may want to run a proc when faking data (to put information from the parameters in to the response or return different responses depending on the parameters). To do this just pass a proc to :fake:

class Person < ActiveRestClient::Base
  get :all, '/people', fake: ->(request) { {result: request.get_params[:id]} }
end

Raw Requests

Sometimes you have have a URL that you just want to force through, but have the response handled in the same way as normal objects or you want to have the filters run (say for authentication). The easiest way to do that is to call _request on the class:

class Person < ActiveRestClient::Base
end

people = Person._request('http://api.example.com/v1/people') # Defaults to get with no parameters
# people is a normal ActiveRestClient object, implementing iteration, HAL loading, etc.

Person._request('http://api.example.com/v1/people', :post, {id:1234,name:"John"}) # Post with parameters

If you want to use a lazy loaded request instead (so it will create an object that will only call the API if you use it), you can use _lazy_request instead of _request. If you want you can create a construct that creates and object that lazy loads itself from a given method (rather than a URL):

@person = Person._lazy_request(Person._request_for(:find, 1234))

This initially creates an ActiveRestClient::Request object as if you'd called Person.find(1234) which is then passed in to the _lazy_request method to return an object that will call the request if any properties are actually used. This may be useful at some point, but it's actually easier to just prefix the find method call with lazy_ like:

@person = Person.lazy_find(1234)

Doing this will try to find a literally mapped method called "lazy_find" and if it fails, it will try to use "find" but instantiate the object lazily.

Plain Requests

If you are already using ActiveRestClient but then want to simply call a normal URL and receive the resulting content as a string (i.e. not going through JSON parsing or instantiating in to an ActiveRestClient::Base descendent) you can use code like this:

class Person < ActiveRestClient::Base
end

people = Person._plain_request('http://api.example.com/v1/people') # Defaults to get with no parameters
# people is a normal ActiveRestClient object, implementing iteration, HAL loading, etc.

Person._plain_request('http://api.example.com/v1/people', :post, {id:1234,name:"John"}) # Post with parameters

The parameters are the same as for _request, but it does no parsing on the response

Proxying APIs

Sometimes you may be working with an old API that returns JSON in a less than ideal format or the URL or parameters required have changed. In this case you can define a descendent of ActiveRestClient::ProxyBase, pass it to your model as the proxy and have it rework URLs/parameters on the way out and the response on the way back in (already converted to a Ruby hash/array). By default any non-proxied URLs are just passed through to the underlying connection layer. For example:

class ArticleProxy < ActiveRestClient::ProxyBase
  get "/all" do
    url "/all_people" # Equiv to url.gsub!("/all", "/all_people") if you wanted to keep params
    response = passthrough
    translate(response) do |body|
      body["first_name"] = body.delete("fname")
      body
    end
  end
end

class Article < ActiveRestClient::Base
  proxy ArticleProxy
  base_url "http://www.example.com"

  get :all, "/all", fake:"{\"name\":\"Billy\"}"
  get :list, "/list", fake:"[{\"name\":\"Billy\"}, {\"name\":\"John\"}]"
end

Article.all.first_name == "Billy"

This example does two things:

  1. It rewrites the incoming URL for any requests matching "/all" to "/all_people"
  2. It uses the translate method to move the "fname" attribute from the response body to be called "first_name". The translate method must return the new object at the end (either the existing object alterered, or a new object to replace it with)

As the comment shows, you can use url value to set the request URL to a particular value, or you can call gsub! on the url to replace parts of it using more complicated regular expressions.

You can use the get_params or post_params methods within your proxy block to amend/create/delete items from those request parameters, like this:

get "/list" do
  get_params["id"] = get_params.delete("identifier")
  passthrough
end

This example renames the get_parameter for the request from identifier to id (the same would have worked with post_params if it was a POST/PUT request). The passthrough method will take care of automatically recombining them in to the URL or encoding them in to the body as appropriate.

If you want to manually set the body for the API yourself you can use the body method

put "/update" do
  body "{\"id\":#{post_params["id"]}}"
  passthrough
end

This example takes the post_params["id"] and converts the body from being a normal form-encoded body in to being a JSON body.

The proxy block expects one of three things to be the return value of the block.

  1. The first options is that the call to passthrough is the last thing and it calls down to the connection layer and returns the actual response from the server in to the "API->Object" mapping layer ready for use in your application
  2. The second option is to save the response from passthrough and use translate on it to alter the structure.
  3. The third option is to use render if you want to completely fake an API and return the JSON yourself

To completely fake the API, you can do the following. Note, this is also achievable using the fake setting when mapping a method, however by doing it in a Proxy block means you can dynamically generate the JSON rather than just a hard coded string.

put "/fake" do
  render "{\"id\":1234}"
end

Translating APIs

IMPORTANT: This functionality has been deprecated in favour of the "Proxying APIs" functionality above. You should aim to remove this from your code as soon as possible.

Sometimes you may be working with an API that returns JSON in a less than ideal format. In this case you can define a barebones class and pass it to your model. The Translator class must have class methods that are passed the JSON object and should return an object in the correct format. It doesn't need to have a method unless it's going to translate that mapping though (so in the example below there's no list method). For example:

class ArticleTranslator
  def self.all(object)
    ret = {}
    ret["first_name"] = object["name"]
    ret
  end
end

class Article < ActiveRestClient::Base
  translator ArticleTranslator
  base_url "http://www.example.com"

  get :all, "/all", fake:"{\"name\":\"Billy\"}"
  get :list, "/list", fake:"[{\"name\":\"Billy\"}, {\"name\":\"John\"}]"
end

Article.all.first_name == "Billy"

Default Parameters

If you want to specify default parameters you shouldn't use a path like:

class Person < ActiveRestClient::Base
  get :all, '/people?all=true' # THIS IS WRONG!!!
end

You should use a defaults option to specify the defaults, then they will be correctly overwritten when making the request

class Person < ActiveRestClient::Base
  get :all, '/people', :defaults => {:active => true}
end

@people = Person.all(active:false)

HTTP/Parse Error Handling

Sometimes the backend server may respond with a non-200/304 header, in which case the code will raise an ActiveRestClient::HTTPClientException for 4xx errors or an ActiveRestClient::HTTPServerException for 5xx errors. These both have a status accessor and a result accessor (for getting access to the parsed body):

begin
  Person.all
rescue ActiveRestClient::HTTPClientException, ActiveRestClient::HTTPServerException => e
  Rails.logger.error("API returned #{e.status} : #{e.result.message}")
end

If the response is unparsable (e.g. not in the desired content type), then it will raise an ActiveRestClient::ResponseParseException which has a status accessor for the HTTP status code and a body accessor for the unparsed response body.

Validation

You can create validations on your objects just like Rails' built in ActiveModel validations. For example:

class Person < ActiveRestClient::Base
  validates :first_name, presence:true
  validates :password, length:{within:6..12}
  validates :post_code, length:{minimum:6, maximum:8}
  validates :salary, numericality:true, minimum:20_000, maximum:50_000

  validates :first_name do |object, name, value|
    object.errors[name] << "must be over 4 chars long" if value.length <= 4
  end

  get :index, '/'
end

Note the block based validation is responsible for adding errors to object.errors[name] (and this will automatically be ready for << inserting into).

Validations are run when calling valid? or when calling any API on an instance (and then only if it is valid? will the API go on to be called).

Debugging

You can turn on verbose debugging to see what is sent to the API server and what is returned in one of these two ways:

class Article < ActiveRestClient::Base
  verbose true
end

class Person < ActiveRestClient::Base
  verbose!
end

By default verbose logging isn't enabled, so it's up to the developer to enable it (and remember to disable it afterwards). It does use debug level logging, so it shouldn't fill up a correctly configured production server anyway.

If you prefer to record the output of an API call in a more automated fashion you can use a callback called record_response like this:

class Article < ActiveRestClient::Base
  record_response do |url, response|
    File.open(url.parameterize, "w") do |f|
      f << response.body
    end
  end
end

Beta Features

XML Responses

ActiveRestClient uses Crack to allow parsing of XML responses. For example, given an XML response of (with a content type of application/xml or text/xml):

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Example Feed</title>
  <link href="http://example.org/"/>
  <updated>2003-12-13T18:30:02Z</updated>
  <author>
    <name>John Doe</name>
  </author>
  <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>

  <entry>
    <title>Atom-Powered Robots Run Amok</title>
    <link href="http://example.org/2003/12/13/atom03"/>
    <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
    <updated>2003-12-13T18:30:02Z</updated>
    <summary>Some text.</summary>
  </entry>

  <entry>
    <title>Something else cool happened</title>
    <link href="http://example.org/2015/08/11/andyjeffries"/>
    <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6b</id>
    <updated>2015-08-11T18:30:02Z</updated>
    <summary>Some other text.</summary>
  </entry>

</feed>

You can use:

class Feed < ActiveRestClient::Base
  base_url "http://www.example.com/v1/"
  get :atom, "/atom"
end

@atom = Feed.atom

puts @atom.feed.title
puts @atom.feed.link.href
@atom.feed.entry.each do |entry|
  puts "#{entry.title} -> #{entry.link.href}"
end

If your XML object comes back with a root node and you'd like to ignore it, you can define the mapping as:

class Feed < ActiveRestClient::Base
  get :atom, "/atom", ignore_xml_root: "feed"
end

For testing purposes, if you are using a fake content response when defining your endpoint, you should also provide fake_content_type: "application/xml" so that the parser knows to use XML parsing.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

active-rest-client's People

Contributors

adamof avatar andyjeffries avatar anthonyatkinson avatar asurin avatar chibicode avatar kludgekml avatar nathanhoel avatar nstokoe avatar rabbitt avatar rafaelpetry avatar raul-gracia avatar robertomiranda avatar thilo avatar tiagotex avatar tuzz avatar washu avatar yassen-bantchev-which avatar yassenb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

active-rest-client's Issues

Resource objects as hash keys

I'm having problems when using ActiveRestClient::Base instances as hash keys. You're able to reproduce the error by running:

{ Model.find(1) => "foo" }
TypeError: no implicit conversion of nil into Integer

I figured out that the root of the problem is that you're undefing hash[1] method from ActiveRestClient::Base.

There is any specific reason to remove hash method?

[1] https://github.com/whichdigital/active-rest-client/blob/master/lib/active_rest_client/base.rb#L13

Create associated resource?

Situation: The API has a workgroup resource, with a has_many :pupils association. Now in order to associate pupils with that workgroup, we currently post to /workgroups/:id/members#create.
How can we do this with Active Rest Client? Do we have to create a new model for each nested resource? As I understand, only associated attributes can be read right?
We'd like to do something like ActiveRestClient::Workgroup.my(id: 42).pupils.create(attr: "value")

Can't use base class

Ideally, I'd like to abstract shaed methods into a Base model shared between others,e.g.

class WhichBase < ActiveRestClient::Base
  base_url "https://www.example.com/api/v1"
  lazy_load!
  perform_caching true
  before_request :add_authentication_details
  @site_identifier = 'trusted-traders'



  def add_authentication_details(name, request)
    request.headers["X-API-Key"] = 'dfdfddfsfdsgdf='
  end
end



class WhichArticle < WhichBase
  get :find, "/articles/#{@site_identifier}/:id", has_many:{images: Image}, lazy: [:images]
end

That fails with:

2.0.0p247 :008 > WhichArticle.find(12344)
TypeError: no implicit conversion of Fixnum into Hash
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/request.rb:92:in `merge'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/request.rb:92:in `prepar
e_params'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/request.rb:56:in `block 
in call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `block in instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications/instrumenter.rb:20:in `instrument
'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/request.rb:50:in `call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/mapping.rb:33:in `_call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/mapping.rb:23:in `block 
in _map_call'
        from (irb):8
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands/console.rb:47:in `start'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands/console.rb:8:in `start'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands.rb:41:in `<top (required)>'
        from script/rails:6:in `require'
        from script/rails:6:in `<main>'

response headers?

I need to access the response headers . . . for example when I do Model.all, ResultIterator contains the status and of course the resulting objects, but I can't figure out how to get the response headers. Specifically I need the Link response header so I can do previous, next, first, last, etc.

I guess what I really need is the raw result back and not just ResultIterator.

Anyway is this possible with active-rest-client?

Sorry if I'm missing something obvious.

Thanks!

Ben

Woudo be nice to have .empty?

When using Tire's built in import:

NoMethodError: undefined method `empty?' for #ActiveRestClient::ResultIterator:0x007ff809d58be0

ActiveRestClient respond to 204 No Content

I have a before_request for all ActiveRestClient calls, but the server is responding with a 204 No Content on some actions. This causes an error: MultiJson::LoadError in FilesController#update 795: unexpected token at ''

before_request do |name, request| request.headers["Accept"] = 'application/json' request.headers["Content-Type"] = 'application/json' end

How can I accept some responses to be no content when using ActiveRestClient?

Also posted on SO
https://stackoverflow.com/questions/27573656/activerestclient-respond-to-204-no-content

Feature request: Ability to stop request and return result in before_request

Something like this (returning false stops request):

class Post < ActiveRestClient::Base
  before_request :verify_something

  def verify_something(name, request, response)
    if some_condition?
      response.body = ''
      return false
    end

   true
  end
end

or call method to set request

class Post < ActiveRestClient::Base
  before_request :verify_something

  def verify_something(name, request)
    set_response('') if some_condition?
  end
end

Any request crashes when faraday config block used

NoMethodError: undefined method `status=' for Faraday::Response:0x00000108715530
~/.rvm/gems/ruby-2.1.2/gems/active_rest_client-1.0.3/lib/active_rest_client/request.rb:277:in `handle_response'
~/.rvm/gems/ruby-2.1.2/gems/active_rest_client-1.0.3/lib/active_rest_client/request.rb:134:in `block in call'
~/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.4/lib/active_support/notifications.rb:159:in `block in instrument'
~/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.4/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
~/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.4/lib/active_support/notifications.rb:159:in `instrument'
~/.rvm/gems/ruby-2.1.2/gems/active_rest_client-1.0.3/lib/active_rest_client/request.rb:89:in `call'
~/.rvm/gems/ruby-2.1.2/gems/active_rest_client-1.0.3/lib/active_rest_client/mapping.rb:46:in `_call'
~/.rvm/gems/ruby-2.1.2/gems/active_rest_client-1.0.3/lib/active_rest_client/mapping.rb:28:in `block in _map_call'
project_root/lib/tasks/geo.rake:69:in `block (2 levels) in '
~/.rvm/gems/ruby-2.1.2/bin/ruby_executable_hooks:15:in `eval'
~/.rvm/gems/ruby-2.1.2/bin/ruby_executable_hooks:15:in `'

It only happens when i have

ActiveRestClient::Base.faraday_config do |faraday|
end

in config/initializers/rest_client.rb

Faraday version - 0.9.0

will_paginate support

Having will_paginate support for ActiveRestClient::ResultIterator would be great. Right now the only option is to call paginate on the @Items array but if the response returned zero results, @Items is nil can will cause paginate to fail. The workaround is to use

@items.try(:paginate)

but this seems less than ideal.

Request responses from W? API cause parse error


2.0.0p247 :009 > a = WhichArticle.find(12344)
 => #<ActiveRestClient::LazyLoader:0x007fdfc92c8980 @request=#<ActiveRestClient::Request:0x007fdfc92c8a48 @method={:name=>:find, :url=>"/articles/trusted-traders/:id", :method=>:get, :options=>{:has_m
any=>{:images=>Image(id: integer, file: string, imageable_id: integer, imageable_type: string, caption: string, position: integer, created_at: datetime, updated_at: datetime, name: string)}, :lazy=>[:
images]}}, @object=WhichArticle, @params=12344, @headers=#<ActiveRestClient::HeadersList:0x007fdfc92c8a20 @store={}>>, @result=nil> 
2.0.0p247 :010 > a.images
ActiveRestClient::ResponseParseException: ActiveRestClient::ResponseParseException
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-52f5068f4145/lib/active_rest_client/request.rb:208:in `rescue in handle_response'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-52f5068f4145/lib/active_rest_client/request.rb:175:in `handle_response'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-52f5068f4145/lib/active_rest_client/request.rb:74:in `block in call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `block in instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-52f5068f4145/lib/active_rest_client/request.rb:50:in `call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-52f5068f4145/lib/active_rest_client/lazy_loader.rb:10:in `method_missing'
        from (irb):10
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands/console.rb:47:in `start'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands/console.rb:8:in `start'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands.rb:41:in `<top (required)>'
        from script/rails:6:in `require'
        from script/rails:6:in `<main>'

Old version of Patron prevent's from using WebMock and VCR

I understand that Patron is locked to an old version due to incompatibilities with Curl.

However, this also prevents us from testing our own ActiveRestClient clients with WebMock and VCR (integration tests), which expect a more recent version of Patron (0.4.18).

Is there a plan to upgrade Patron at some point or is compatibility with certain Curl versions a priority?

Regards

Using a proxy and caching results in an error when requesting a cached resource

When caching is enabled and a proxy is used one gets MultiJson::ParseError because when the response status is 304 the body is empty and is not valid JSON. An example stack trace is

MultiJson::ParseError - Oj::ParseError:
  multi_json (1.10.1) lib/multi_json/parse_error.rb:9:in `block (2 levels) in build'
  multi_json (1.10.1) lib/multi_json/parse_error.rb:7:in `block in build'
  multi_json (1.10.1) lib/multi_json/parse_error.rb:6:in `build'
  multi_json (1.10.1) lib/multi_json.rb:121:in `rescue in load'
  multi_json (1.10.1) lib/multi_json.rb:118:in `load'
  active_rest_client (1.0.5) lib/active_rest_client/proxy_base.rb:72:in `translate'
  app/models/homepage.rb:7:in `block in <class:HomeProxy>'
  active_rest_client (1.0.5) lib/active_rest_client/proxy_base.rb:111:in `handle'
  active_rest_client (1.0.5) lib/active_rest_client/request.rb:120:in `block in call'
  activesupport (4.0.11) lib/active_support/notifications.rb:159:in `block in instrument'
  activesupport (4.0.11) lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  activesupport (4.0.11) lib/active_support/notifications.rb:159:in `instrument'
  active_rest_client (1.0.5) lib/active_rest_client/request.rb:89:in `call'
  active_rest_client (1.0.5) lib/active_rest_client/mapping.rb:46:in `_call'
  active_rest_client (1.0.5) lib/active_rest_client/mapping.rb:28:in `block in _map_call'

NoMethodError: undefined method `present?'

I think activesupport changed the way to require all of its modules. Right now, in active-rest-client code, we have it like this:

require 'active_support'

This doesn't work for activesupport latest version (4.0.2). You can do a quick test by opening IRB and trying to require it like that.

A consequence of that is the title of this issue: method present? cannot be found.

There are two ways to solve this:

  1. Force in the gemspec for a prior version of activesupport - I would suggest this, but I'm not sure which version should it be.
  2. Instead of doing require 'active_support', with version 4.0.2 we now should use require 'active_support/all', then it will work.

As the current gemspec doesn't force any specific version for activesupport, the gem is broken right now because of this external dependency. I did a quick fix locally by replacing the require part, as I mentioned above.

I can create a pull request if needed, just need to know what's the author desired approach.

For those that don't face this issue yet, it's probably because you have an old version of activesupport installed.

Thanks,
Nicholas

switch to multijson

instead of hardcoding an old version of Oj (oj is now at 2.5.5) let the user pick json parsing implementation,

to_json could be more useful

take this little example:

a = WhichContent::WhichArticle::ApiArticle.all.first 
images = a.images 

WORKS GREAT

2.0.0p247 :050 > images.to_json
 => "[[\"main\",[[]]]]" 

Is not that useful. In order to use serialisaton, currently i need to manually loop/collect and to_json on the sub-objects which is kinda binding the structure. Example, in ElasticSEarch you throw json objects at it, and it stores them according to indexing rules.

Inaproriate handling of "HTTP Basic: Access denied."

Instead of getting HTTPUnauthorisedClientException, Request.handle_response is instead hitting internal parse error with MultiJson, when trying to parse response for 401, which is not having content type "json".

This makes error handling for authentication failures tricky, since they go into same bin as generic result parse errors.

after_response needed

the use of before_request is definitely nice and would love an after_response functionality to handle the response after the fact. There are certain things in our response.body that we'd like to manipulate a bit.

Global lazy_load! and verbose! not working

config/initializers/api.rb

ActiveRestClient::Base.base_url = API::URL
ActiveRestClient::Base.lazy_load!
ActiveRestClient::Base.verbose Rails.env.development?

app/models/person.rb

class Person < ActiveRestClient::Base
  get :all, '/persons'
end

rails console

2.1.0 :001 > Person.all
ActiveRestClient Person:/persons - Trying to read from cache
ActiveRestClient Person#all - Requesting http://site/api/persons
ActiveRestClient Person#all - Response received 20 bytes
ActiveRestClient (80.0ms) Person#all
 => ActiveRestClient::ResultIterator:0x000000099ef7b0

So as you see no verbosity and lazy load.

Same thing if i create own base class and set config.

app/models/my_base.rb

class MyBase < ActiveRestClient::Base
  lazy_load!
  verbose Rails.env.development?
end

app/models/person.rb

class Person < MyBase
  get :all, '/persons'
end

404s cause ResponseParseException

Requesting a bad endpoint, response from API looks wrong and causes

WhichContent::WhichArticle.find(12344)
 => #<ActiveRestClient::LazyLoader:0x007f9a16f45b70 @request=#<ActiveRestClient::Request:0x007f9a16f46728 @method={:name=>:find, :url=>"/trusted-trade
rs/articles/:id", :method=>:get, :options=>{:has_many=>{:images=>WhichContent::WhichImage}, :lazy=>[]}}, @object=WhichContent::WhichArticle, @params=1
2344, @headers=#<ActiveRestClient::HeadersList:0x007f9a16f45d28 @store={}>>, @result=nil> 
2.0.0p247 :002 > WhichContent::WhichArticle.find(12344).name
ActiveRestClient::ResponseParseException: ActiveRestClient::ResponseParseException
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-067a80d954c1/lib/active_rest_client/request.rb:246:in `rescue
 in handle_response'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-067a80d954c1/lib/active_rest_client/request.rb:207:in `handle
_response'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-067a80d954c1/lib/active_rest_client/request.rb:84:in `block i
n call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `block in instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-067a80d954c1/lib/active_rest_client/request.rb:58:in `call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-067a80d954c1/lib/active_rest_client/lazy_loader.rb:10:in `met
hod_missing'
        from (irb):2
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands/console.rb:47:in `start'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands/console.rb:8:in `start'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands.rb:41:in `<top (required)>'
        from script/rails:6:in `require'
        from script/rails:6:in `<main>'

LOG

Connecting to database specified by database.yml
�[1;4;32mActiveRestClient�[0m WhichContent::WhichArticle#find - Requesting https://staging.services.which.co.uk/content/v1/trusted-traders/articles/12344
ActiveRestClient Verbose Log:

Accept : application/hal+json, application/json;q=0.5
X-API-Key : VEVTVC1LRVk=

< Date : Mon, 23 Sep 2013 13:06:38 GMT
< Content-Type : text/html;charset=utf-8
< Content-Length : 1033
< Keep-Alive : timeout=2, max=500
< Connection : Keep-Alive
< Set-Cookie : bIPs=78c233f966bf604cfc26eb62fb889ba4;path=/;
< <title>Apache Tomcat/7.0.39 - Error report</title><style></style>

HTTP Status 404 - /content/v1/trusted-traders/articles/12344


type Status report

message /content/v1/trusted-traders/articles/12344

description The requested resource is not available.


Apache Tomcat/7.0.39


�[1;4;32mActiveRestClient�[0m WhichContent::WhichArticle#find - Response received 1033 bytes
�[1;4;32mActiveRestClient (579.2ms)�[0m WhichContent::WhichArticle#find

ResponseParseException on Valid JSON

I have an API that is returning valid JSON but I keep getting ActiveRestClient::ResponseParseException when it comes back.

For example, when I inspect the exception body I get this:

"{\"computer_reports\":[{\"id\":6,\"name\":\"Desktops\"},{\"id\":1,\"name\":\"Automation\"},{\"id\":2,\"name\":\"Build\"},{\"id\":3,\"name\":\"Storage\"},{\"id\":5,\"name\":\"Portables\"}]}"

Which I can then parse just fine with JSON.parse

Am I missing something here or is this a bug?

Thanks!

Her

Loads of great stuff in this gem.

But, have you seen https://github.com/remiprev/her ? Its an ActiveModel implementation for REST. Had success using it and covers a lot of the ground you are covering with the benefit of lots of Railsey defaults, multiple middleware (e.g. custom caching and cache breaking) etc.

One nice feature of faraday middleware, is that the auth layer becomes easy to integrate with things like Device and CAS client.

._attributes usage?

Often we need to be able to extract the attributers of an object to use/merge them (e.g. indexing).

Is the intention to use ._attributes to inspect an objects attributes?

I'm looking for something similar to .attributes on ActiveModel.

Thanks.

Send hash to JSON API

ActiveRestClient is correctly configured in the rails app. I can find a contact, but when I'm trying to update no body is sent. I couldn't find out why, but trying using POST for debugging.

# model contact
class Contact < ActiveRestClient::Base
  request_body_type :json
  verbose true

  get :find, "/contacts/:id"
  post :save, "/contacts/post/:id"
end
#controler contact
def update
    if Contact.save(id: params["id"], contact: params["contact"])
   (...)
end

I'm trying to send an Hash (params["contact"]) to the API:
{"contact" => {"contact_info" => {"name" => "sdfgsdfg"}}}

When I try to send it using the CURL via terminal the params received in the API are:
{"contact"=>{"contact_info"=>{"name"=>"Philipp123"}}, "splat"=>[], "captures"=>["54dd076c3465300003380000"], "id"=>"54dd076c3465300003380000"}

When I do it in the rails app I receive:
{"{\"contact\":{\"contact_info\":{\"name\":\"sdfgsdfg\"}}}"=>nil, "splat"=>[], "captures"=>["54dd076c3465300003380000"], "id"=>"54dd076c3465300003380000"}

What am I doing wrong? How do I send an hash?

unable to access object.errors

Hi!
Thanks for this helpful gem which helped me a lot to quickly create a REST-based model.

With basic requests working I now wanted to carry on with error handling. I wanted to use active-rest-client's validation capability, when I got stuck with not being able to access the errors hashmap.

In short, this didn't work:

class MyRestClass < ActiveRestClient::Base
  validates :myAttribute, presence: true
[...]
end

> restobject = MyRestClass.new()
> restobject.valid?
> restobject.errors
 => nil

I wondered why, because both valid? and errors are defined in validation.rb, but somehow I wasn't able to use the errors method. It wasn't in the list of restobject.methods either, so I started digging and found that the errors method seems to get undefined in base.rb:L14. When I removed errors from the array, all started working as expected.

I'm no ruby/rails expert, but to me it looks like a bug. Or is it me doing something wrong?

Caching working (partially) regardless of #perform_caching setting

The gem seems to be trying to cache responses regardless of the perform_caching setting.

I've set it to false in my class.

class Article < ActiveRestClient::Base
      base_url Rails.application.config.content_api_url
      perform_caching false

      get :all, '/articles/elderly-care/?last_modified=2012-09-26T16:34:11Z'
      get :find, '/articles/elderly-care/:id'
      ...
end

A first request (in a Rails app) works. A second request to the same URl, however, returns :not_modified as a response body.

Looking at the code I see it seems to attempt caching regardless of the setting, in Request.rb:

cached = ActiveRestClient::Base.read_cached_response(self)
if cached
  if cached.expires && cached.expires > Time.now
    ActiveRestClient::Logger.debug "  \033[1;4;32m#{ActiveRestClient::NAME}\033[0m #{@instrumentation_name} - Absolutely cached copy found"
    return handle_cached_response(cached)
  elsif cached.etag.present?
    ActiveRestClient::Logger.debug "  \033[1;4;32m#{ActiveRestClient::NAME}\033[0m #{@instrumentation_name} - Etag cached copy found with etag #{cached.etag}"
    etag = cached.etag
  end
end
response = if proxy
  proxy.handle(self) do |request|
    request.do_request(etag)
  end
else
  do_request(etag)
end
result = handle_response(response)
ActiveRestClient::Base.write_cached_response(self, response, result)

Maybe caching could be handled in a more middleware oriented manner, by wrapping or decorating requests instead of conditionals inside the main request#call method? For example Faraday allows you to add HTTP caching as middleware.

Following links - not working

When trying to follow a link, it works fine in the first call. However, in any subsequent calls it blows up.

For the following example, in the first iteration c represents a Campaign object and c.advertiser gives me a LazyAssociationLoader object. However, in the second iteration, c is still representing a Campaign object but with all the data being the one returned from the Advertiser endpoint - advertiser is a link rel present in the JSON returned by the service.

Snippet:

require 'bundler'
Bundler.require

class Campaign < ActiveRestClient::Base
  base_url 'http://localhost:4001'

  get :find, '/campaigns/:id'
end

5.times do |t|
  c = Campaign.find(713896)
  puts c.name # => Foobar
  puts c.advertiser.name # => blows up in the second iteration, because c no longer represents a Campaign
end

JSON snippet (service):

{
    "name": "Foobar",
    "_links": { 
     "advertiser": { "href": "http://localhost:4001/advertisers/123" }
    }
}

Add hook to be able to record response

Add a hook (block) so that the response coming back for a particular URL can be retrieved before being instantiated, for example so it can be saved to a file (in order to later be served via a Mock server for tests).

How to swap between linked content and full URLs

We have an Article like:

class WhichArticle < ActiveRestClient::Base
  verbose true
  include WhichConcern

  get :find, "/articles/#{@site_identifier}/:id", has_many:{images: WhichImage}
end

So, if we did WhichArticle.find(123) is it using the base_url setup for it.

Not lets say, in another piece of content we find a link such as

https://staging.services.which.co.uk/content/v2/articles/trusted-traders/22222

...note the base URL is different (this is what Anand says was the reasoning behind using full URIs instead of relative ones to a single endpoint).

Now, how do i fetch this using my pre-existing WhichArticle model? I can think of inelegant ways of munging the base_url, but that's coding a lot of logic outside of the model, and i'm unsure how that plays with your connection caching/reuse/pooling.

Releasing v1.0.0

When all the changes are merged to master and the next release is cut, can you make it v1.0.0 please :-)

Unable to set 'response.body' in 'after_request' callback

In order to conform to the REST conventions of a Rails app - specifically a 'GET index' results contain a 'root' node corresponding to the 'underscorerized / pluralized' name of the resource - I need to add an 'after_request' callback.

While experimenting with implementing this callback - I thought I'd try the 'base' case listed in the documentation ...

class Person < ActiveRestClient::Base
after_request :fix_empty_content

private

def fix_empty_content(name, response)
if response.status == 204 && response.body.blank?
response.body = '{"empty": true}'
end
end
end

This doesn't work and returns this error ...

NoMethodError: undefined method body=' for #<Faraday::Response:0x007f9361ac9058> from /Users/dekhaus/projects/rails4/groot/app/models/wip_model.rb:26:inadjust_body'

Presumably - because no setter exists for the 'body' attribute.

Regards,
Dave

All requests seem to result in TypeError

class WhichArticle < ActiveRestClient::Base

    base_url "https://www.example.com/api/v1"
    before_request :add_authentication_details

  get :find, "/articles/trusted-traders/:id"

    def add_authentication_details(name, request)
    request.headers["X-API-Key"] = 'VEVTVC1LRVk='
  end
end

2.0.0p247 :001 > a = WhichArticle.find(12344)
TypeError: no implicit conversion of Fixnum into Hash
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/request.rb:92:in `merge'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/request.rb:92:in `prepar
e_params'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/request.rb:56:in `block 
in call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `block in instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications/instrumenter.rb:20:in `instrument
'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `instrument'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/request.rb:50:in `call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/mapping.rb:33:in `_call'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/bundler/gems/active-rest-client-1d9cb94b9479/lib/active_rest_client/mapping.rb:23:in `block 
in _map_call'
        from (irb):1
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands/console.rb:47:in `start'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands/console.rb:8:in `start'
        from /Users/finalarena/.rvm/gems/ruby-2.0.0-p247/gems/railties-3.2.13/lib/rails/commands.rb:41:in `<top (required)>'
        from script/rails:6:in `require'
        from script/rails:6:in `<main>'

Association belongs_to is needed

Currently, the has_many association is supported.

We need a belongs_to association to hook it up to its correct model. For instance:

{
  id: 1,
  label: "Favorites",
  owner_person: {
    id: 2
  },
  admin_person: {
    id: 1
  }
}

The owner_person and admin_person needs its association defined so it is a Person model.

For example we could do something like this:

get :find, "/lists/:id", :belongs_to => {:owner_person => Person, :admin_person => Person}

I hope I'm making sense here. Let me know If I am not and happy to clarify.

Heavy use of stubs/mocks in tests

It's difficult to maintain code where the tests are heavily coupled to the implementation. I'd like to see if we can test the gem with a greater use of integration tests.

This would also be a good opportunity to see if there are features that can be pruned or extracted out of the gem as modules. These could possibly be separate gems entirely.

Thoughts?

Change to support key=>value lists of links

As per Mike L's email regarding the difference in structure of link lists such as images, e.g.


    "images": {
        "main": "https://staging.services.which.co.uk/content/v1/images/trusted-traders/1234",
        "secondary": "https://staging.services.which.co.uk/content/v1/images/trusted-traders/1235",
        "third": "https://staging.services.which.co.uk/content/v1/images/trusted-traders/1236"
    },

Add support to the Client to be able to support these lists, as well as the current simple array-of-urls.

Following the link fetch, each item (here "main", "secondary", "third") need to be accessible by these keys, and each would be an instance (in my case) of WhichImage as get :find, "/articles/#{@site_identifier}/:id", has_many:{images: WhichImage}

Other instances I've spotted in your API with a similar structure is images, links, panels.

Trying to use HTTP Basic Authentication

Followed the ReadMe to set up the credentials (username and password). It doesn't recognize username nor password. Any advice is greatly appreciated:

class Person < ActiveRestClient::Base
  username 'api'
  password 'eb693ec-8252c-d6301-02fd0-d0fb7-c3485'

  # ...
end

Active-rest-client with VCR and Faraday

I would like to use ARC with VCR. I see that we can use faraday and use VCR for testing. Below is the configuration part which i have tried.

Active-rest-client faraday configuration.
https://github.com/whichdigital/active-rest-client#faraday-configuration

VCR provides a middleware that can be used with Faraday for testing.
https://www.relishapp.com/vcr/vcr/v/1-6-0/docs/middleware/faraday-middleware

Below is my spec_helper.rb configuration.
http://pastie.org/private/mttyymqlkw0dwxudw8h7na

Below is my user_spec.rb
http://pastie.org/private/rhvfdswceev0sgkvjxajw

When I tried to run our user-spec. I got the below trace. Undefined method “to_hash” for nil:NilClass
http://pastie.org/private/rubppjag0gxg5msz1pngq

This lead to me a bug which has been posted in vcr gem that VCR does not work with Faraday 0.9.0 and up and it’s a known issue.
vcr/vcr#386

I tried to downgrade Faraday with 0.8.9, but unfortunately Active-rest-client gem does not work below Faraday 0.9.0. Is it possible to run ACR with Faraday 0.8.9 and below?.

Proxy translate headers

The proxy base class and translating only allows for the body to be modified. The change I would like which would be a breaking change is to allow modification of the entire response. this could be the headers or status and not just restricted to the body.

Rationale. If the Which API correctly set the expires headers then this would not be a problem and caching would just work. however the Which API does not do this correctly. So I am not able to cache things. When I get the response I would like to be able to manipulate the headers - specifically the expires headers which I can set to a day or some other definable strategy which I have control over.

This to me seems the most flexible solution. I can't think any other nice solutions to get round this. Suggestions?

Request Url incorrect when caching on

When the base_url in your model file has a slash something (ie 'www.example.com/api) and you have caching on, if this check in request.rb:118

if response.status == 304

fails and line 126 is triggered

request.do_request(etag)

your second attempt to access the same url will result in a duplication of the portion of the url after the slash and before the request url. So if I have

get :item

in my model and this scenario occurs, the second attempt will try to reach www.example.com/api/api/item. It is happening because of request.rb:220 and the side effect of being called in succession for the same request:

@url = "#{base_url}#{@url}".gsub(@base_url, "")

I'm not sure how to fix it without breaking the basic case of a single request.

URL issue

I have just started using Active Rest Client and there seems to be something strange going on with the URL calls.

If i do;

class Promotion < ActiveRestClient::Base
  base_url "http://localhost:3000/cef_api_engine"

  get :all, "/promotions"
end

as stated in the docs. I get this;

2.0.0p247 :021 > Promotion.all
  ActiveRestClient Promotion:/promotions - Reading from cache
  ActiveRestClient Promotion#all - Requesting http://localhost:3000/cef_api_engine/promotions
  ActiveRestClient Promotion#all - Response received 8903 bytes
  ActiveRestClient (30.5ms) Promotion#all
ActiveRestClient::ResponseParseException: ActiveRestClient::ResponseParseException

The url looks correct however it throws an error and on my API side it does not call the correct path.. it only calls GET "/promotions" (notice the missing "/cef_api_engine")

If I however change it so I have this;

class Promotion < ActiveRestClient::Base
  base_url "http://localhost:3000/cef_api_engine"

  get :all, "promotions"
end

I get;

2.0.0p247 :023 > Promotion.all
  ActiveRestClient Promotion:promotions - Reading from cache
  ActiveRestClient Promotion#all - Etag cached copy found with etag "d751713988987e9331980363e24189ce"
  ActiveRestClient Promotion#all - Requesting http://localhost:3000/cef_api_enginepromotions
  ActiveRestClient Promotion#all - Etag copy is the same as the server
  ActiveRestClient Promotion:promotions - Writing to cache
  ActiveRestClient (27.1ms) Promotion#all
 => #<ActiveRestClient::ResultIterator:0x007f88d08471b8 @_status=200, @items=[]> 

It works and I get the desired result, however as you can see the requested URL is incorrect.

Not sure If I am missing something here?

Inapproriate logic with bad_requests

I had this kind of model

class Foo < Base
    get :find, '/appstores/:id'
end

I do this

m = Foo.find(1)
# okey, it was found
m.find
# Now, this invokes same call into server, but tries to pass all parameters retrieved
# in first call. However, server side logic has strict parameter validation logic, so it
# will send "bad_request", since request contained params, which should not be there.
# Exception is raised, but...
#
# PROBLEM: model is now in bogus state
m._attributes
# => contains now "bad_request" error json, thus model is invalid, and any further
# action with model will just lead into more confusing errors
# for example,
m.find
# => it will now try to pass previous bad_request error contents as params to server

I see two issues here:

  1. Would be nice if there would be some convenient way of genericly restrict that all model params would not be passed always over (doing it via filter is possible, but requires plenty of extra boilerplate; came into this conclusion after writing one such filter).

  2. If error occurs, overriding model contents with error message shouldn't occur.

Help me fetch panels

Hi,

apologies we've covered this, but for some reason I'm failing :)

We have a path like https://staging.services.which.co.uk/content/v1/articles/trusted-traders/?path=/hompage/ to an articles, returning:

[
    {
        "id": 332652,
        "site_structure": {
            "url": "https://staging.services.which.co.uk/content/v1/site-structure/trusted-traders/335020",
            "path": "/homepage/"
        },
        "title": "Which? Trusted Traders",
        "search_result_weighting": null,
        "first_published_date": "2013-09-26T13:37:26Z",
        "last_modified_date": "2013-09-27T09:09:46Z",
        "seo_description": "TBC",
        "seo_keywords": "TBC",
        "seo_title": "Which? Trusted Traders - Which?",
        "teaser_title": null,
        "teaser_text": null,
        "content": [
            "<h1>Get the recognition you deserve</h1><h3>Stand out from the crowd by becoming a Which? Trusted Trader</h3>",
            "<h2>Rewarding great service</h2><p>We put traders through a rigorous assessment, meaning that consumers get the best service and Which? Trusted Traders get the business and recognition they deserve.</p><p><a href=\"/trusted-traders/about/\" title=\"\">More about becoming a Trusted Trader</a></p>",
            "<h2>Benefits of becoming a Which? Trusted Trader</h2><p>Becoming a Which? Trusted Trader means you can differentiate your business from competitors and maximise your potential to increase quality leads. You’ll get:</p>"
        ],
        "articles": [],
        "people": [],
        "videos": [],
        "images": {
            "main": "https://staging.services.which.co.uk/content/v1/images/trusted-traders/334916"
        },
        "links": {},
        "panels": {
            "promo": [
                "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334901"
            ],
            "other": [
                "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334858",
                "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334859",
                "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334860",
                "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334861",
                "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334862",
                "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334863"
            ]
        },
        "external_taxonomy": []
    }
]

Now, I'm interested in panels, specifically rendering each other "other" panels into the boxes you can see at the bottom of http://trustedtraders.which.co.uk/

I have an article model that works great, and I can render the inline html fine.

class WhichContent::WhichArticle
  include CacheTools
  include Tire::Model::Persistence

  index_name('which_article')
  document_type('which_article')

  def self.api
    return ApiArticle
  end

  def self.sync_api
    all = ApiArticle.all
    puts all.class
    tire.index.import all
  end

  # HOMEPAGE
  def self.homepage(force_refresh=nil)
    self.get_path('/homepage/', force_refresh)
  end

  # GENERIC PATH FETCHERS
  def self.get_path(path='/homepage/',force_refresh=false)
    self.delete_path(path) if force_refresh
    h = Rails.cache.read('which_api_path_#{path}')
    puts h
    if h.nil?
      h = self.sync_path(path)
      puts "h=#{h}"
    end
    h
  end

  def self.delete_path(path='/homepage/')
    Rails.cache.delete('which_api_path_#{path}')
  end

  def self.sync_path(path='/homepage/', expires_in=@default_site_cache_expiry)
    Rails.logger.info "Fetching which api content for path=#{path}"
    begin
      h = ApiArticle.by_path({path: path}).first
      puts "#response= #{h}"
      Rails.logger.info "...fetched #{h.inspect}"
      cached = Rails.cache.write('which_api_path_#{path}', h, :expires_in => expires_in)
      Rails.logger.info "...saved to Rails cache = #{cached}"
      indexed = (self.tire.index.import [h])
      Rails.logger.info "...saved to ElasticSearch = #{indexed}"
      return h
    rescue Exception=>e
      Rails.logger.warn e.message
    end
  end

  class ApiArticle < ActiveRestClient::Base
    include WhichContent::WhichConcern

    get :find, "/articles/:id", has_many:@default_linked_resources
    get :by_path, "/articles/#{@site_identifier}/", :defaults => {path: 'no-path-given'}, has_many:@default_linked_resources
    get :all, "/articles/#{@site_identifier}/", :defaults => {last_modified: '1970-01-01T00:00:00Z', orderby: :last_modified}, has_many:@default_linked_resources

    def to_indexed_json
      self._attributes.to_json
    end

  end

end
module WhichContent::WhichConcern
  extend ActiveSupport::Concern

  included do
    base_url "https://staging.services.which.co.uk/content/v1"
    @default_site_cache_expiry = 1.hour
    @default_linked_resources = {images: WhichContent::WhichImage, panels: WhichContent::WhichPanel}
    #lazy_load!
    #timeout 30
    #perform_caching false
    verbose! if Rails.env.development?
    before_request :add_authentication_details
    @site_identifier = 'trusted-traders'
  end

  def add_authentication_details(name, request)
    request.headers["X-API-Key"] = 'VEVTVC1LRVk='
  end

end
class WhichContent::WhichPanel < ActiveRestClient::Base
 include WhichContent::WhichConcern

 get :find, "/panels/:id", has_many:{images: WhichContent::WhichImage}, has_many:@default_linked_resources
end

PROBLEM: When I fetch panels, I only get arrays, not objets converted to WhichPanel:

2.0.0p247 :016 > h = WhichContent::WhichArticle.homepage
#<WhichContent::WhichArticle::ApiArticle:0x007fd98d72ad30>
 => #<WhichContent::WhichArticle::ApiArticle:0x007fd98d72ad30 @attributes={:id=>332652, :site_structure=>#<WhichContent::WhichArticle::ApiArticle:0x00
7fd98d728e68 @attributes={:url=>"https://staging.services.which.co.uk/content/v1/site-structure/trusted-traders/335020", :path=>"/homepage/"}, @dirty_
attributes=#<Set: {}>>, :title=>"Which? Trusted Traders", :search_result_weighting=>nil, :first_published_date=>Thu, 26 Sep 2013 13:37:26 +0000, :last
_modified_date=>Fri, 27 Sep 2013 09:09:46 +0000, :seo_description=>"TBC", :seo_keywords=>"TBC", :seo_title=>"Which? Trusted Traders - Which?", :teaser
_title=>nil, :teaser_text=>nil, :content=>#<ActiveRestClient::ResultIterator:0x007fd98d7408b0 @_status=nil, @items=["<h1>Get the recognition you deser
ve</h1><h3>Stand out from the crowd by becoming a Which? Trusted Trader</h3>", "<h2>Rewarding great service</h2><p>We put traders through a rigorous a
ssessment, meaning that consumers get the best service and Which? Trusted Traders get the business and recognition they deserve.</p><p><a href=\"/trus
ted-traders/about/\" title=\"\">More about becoming a Trusted Trader</a></p>", "<h2>Benefits of becoming a Which? Trusted Trader</h2><p>Becoming a Whi
ch? Trusted Trader means you can differentiate your business from competitors and maximise your potential to increase quality leads. You’ll get:</p>"]
>, :articles=>#<ActiveRestClient::ResultIterator:0x007fd98d74b850 @_status=nil, @items=[]>, :people=>#<ActiveRestClient::ResultIterator:0x007fd98d74a3
b0 @_status=nil, @items=[]>, :videos=>#<ActiveRestClient::ResultIterator:0x007fd98d748fb0 @_status=nil, @items=[]>, :images=>#<WhichContent::WhichImag
e:0x007fd98d753c08 @attributes={:main=>"https://staging.services.which.co.uk/content/v1/images/trusted-traders/334916"}, @dirty_attributes=#<Set: {}>>
, :links=>#<WhichContent::WhichArticle::ApiArticle:0x007fd98d7522b8 @attributes={}, @dirty_attributes=#<Set: {}>>, :panels=>#<WhichContent::WhichPanel
:0x007fd98d751778 @attributes={:promo=>#<ActiveRestClient::ResultIterator:0x007fd98d7509e0 @_status=nil, @items=["https://staging.services.which.co.uk
/content/v1/panels/trusted-traders/334901"]>, :other=>#<ActiveRestClient::ResultIterator:0x007fd98d7502b0 @_status=nil, @items=["https://staging.servi
ces.which.co.uk/content/v1/panels/trusted-traders/334858", "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334859", "https://s
taging.services.which.co.uk/content/v1/panels/trusted-traders/334860", "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334861"
, "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334862", "https://staging.services.which.co.uk/content/v1/panels/trusted-tra
ders/334863"]>}, @dirty_attributes=#<Set: {}>>, :external_taxonomy=>#<ActiveRestClient::ResultIterator:0x007fd98d75b4a8 @_status=nil, @items=[]>}, @di
rty_attributes=#<Set: {}>> 


2.0.0p247 :017 > h.panels
 => #<WhichContent::WhichPanel:0x007fd98d751778 @attributes={:promo=>#<ActiveRestClient::ResultIterator:0x007fd98d7509e0 @_status=nil, @items=["https:
//staging.services.which.co.uk/content/v1/panels/trusted-traders/334901"]>, :other=>#<ActiveRestClient::ResultIterator:0x007fd98d7502b0 @_status=nil, 
@items=["https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334858", "https://staging.services.which.co.uk/content/v1/panels/trust
ed-traders/334859", "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334860", "https://staging.services.which.co.uk/content/v1/
panels/trusted-traders/334861", "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334862", "https://staging.services.which.co.uk
/content/v1/panels/trusted-traders/334863"]>}, @dirty_attributes=#<Set: {}>> 

2.0.0p247 :018 > h.panels.other
 => #<ActiveRestClient::ResultIterator:0x007fd98d7502b0 @_status=nil, @items=["https://staging.services.which.co.uk/content/v1/panels/trusted-traders/
334858", "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334859", "https://staging.services.which.co.uk/content/v1/panels/trus
ted-traders/334860", "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334861", "https://staging.services.which.co.uk/content/v1
/panels/trusted-traders/334862", "https://staging.services.which.co.uk/content/v1/panels/trusted-traders/334863"]> 

So, the question for the prize of a beer is, what have I forgotten to do! :-p

I'm on 0.9.23

Default POST request body is not a JSON

We are trying to switch from ActiveResource gem and faced this hurdle.

The request object is not generating a JSON body. We need to have a before_request filter to generate it.

Can we make the JSON body as the default option? Or is there a specific reason for not sending the JSON body?

Bad URI(is not URI?)

I currently have my model as such:

class Item < ActiveRestClient::Base
  base_url "http://api.my-url.com/v2"
  username 'username'
  password 'password'

  get :all, "/list/"
  get :find, "/items/:id"
end

When I query Item.all within rails console, I get the following error

>> Item.all
  ActiveRestClient Item:/list - Trying to read from cache
  ActiveRestClient (3.9ms) Item#all
URI::InvalidURIError: bad URI(is not URI?): http://username:[email protected]

Any idea how to resolve this? It's curious to me how the v2 is being dropped in the error, even though it's in my base_url

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.