json_api_client 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f5db14498ef10dc6d6aaa4bcb65b0a6c5bdcb300
4
+ data.tar.gz: 75c000bfceb5104a78ab2929f0c160254ce129e5
5
+ SHA512:
6
+ metadata.gz: f5eae53897bbd0f37a524e979b3c81374ba74607ee4f8e3594d6c581230ad4cca41fb9b355d306ca7da6ef222e9af37613c19e9b4856545908f66a28a16dde2c
7
+ data.tar.gz: de3bcf1db477b01d2f89bd9744b5c94eeb7bdd1644752335f44ea667e9b6c0c0ccca3e042a54e6a3e540cb4c1ed63db41c1caa5f2d1ab4232c84eff8350be4fa
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) {{year}} {{fullname}}
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # JsonApiClient [![Build Status](https://travis-ci.org/chingor13/json_api_client.png)](https://travis-ci.org/chingor13/json_api_client)
2
+
3
+ This gem is meant to help you build an API client for interacting with REST APIs as laid out by [http://jsonapi.org](http://jsonapi.org). It attempts to give you a query building framework that is easy to understand (it is similar to ActiveRecord scopes).
4
+
5
+ *Note: This is still a work in progress.*
6
+
7
+ ## Usage
8
+
9
+ ```
10
+ module MyApi
11
+ class User < JsonApiClient::Resource
12
+ has_many :accounts
13
+ end
14
+
15
+ class Account < JsonApiClient::Resource
16
+ belongs_to :user
17
+ end
18
+ end
19
+
20
+ MyApi::User.all
21
+ MyApi::User.where(account_id: 1).find(1)
22
+ MyApi::User.where(account_id: 1).all
23
+
24
+ MyApi::User.where(name: "foo").order("created_at desc").includes(:preferences, :cars).all
25
+
26
+ u = MyApi::User.new(foo: "bar", bar: "foo")
27
+ u.save
28
+
29
+ u = MyApi::User.find(1).first
30
+ u.update_attributes(
31
+ a: "b",
32
+ c: "d"
33
+ )
34
+
35
+ u = MyApi::User.create(
36
+ a: "b",
37
+ c: "d"
38
+ )
39
+
40
+ u = MyApi::User.find(1).first
41
+ u.accounts
42
+ => MyApi::Account.where(user_id: u.id).all
43
+ ```
44
+
45
+ ## Connection options
46
+
47
+ You can configure your connection using Faraday middleware. In general, you'll want
48
+ to do this in a base model that all your resources inherit from:
49
+
50
+ ```
51
+ MyApi::Base.connection do |connection|
52
+ # set OAuth2 headers
53
+ connection.use Faraday::Request::Oauth2, 'MYTOKEN'
54
+
55
+ # log responses
56
+ connection.use Faraday::Response::Logger
57
+
58
+ connection.use MyCustomMiddleware
59
+ end
60
+
61
+ module MyApi
62
+ class User < Base
63
+ # will use the customized connection
64
+ end
65
+ end
66
+ ```
67
+
68
+ ## Custom Parser
69
+
70
+ You can configure your API client to use a custom parser that implements the `parse` class method. It should return a `JsonApiClient::ResultSet` instance. You can use it by setting the parser attribute on your model:
71
+
72
+ ```
73
+ class MyCustomParser
74
+ def self.parse(klass, response)
75
+
76
+ # returns some ResultSet object
77
+ end
78
+ end
79
+
80
+ class MyApi::Base < JsonApiClient::Resource
81
+ self.parser = MyCustomParser
82
+ end
83
+ ```
84
+
85
+ ## Handling Validation Errors
86
+
87
+ ```
88
+ User.create(name: "Bob", email_address: "invalid email")
89
+ => false
90
+
91
+ user = User.new(name: "Bob", email_address: "invalid email")
92
+ user.save
93
+ => false
94
+ user.errors
95
+ => ["Email address is invalid"]
96
+
97
+ user = User.find(1)
98
+ user.update_attributes(email_address: "invalid email")
99
+ => false
100
+ user.errors
101
+ => ["Email address is invalid"]
102
+ user.email_address
103
+ => "invalid email"
104
+ ```
105
+
106
+ ## Nested Resources
107
+
108
+ You can force nested resource paths for your models by using a `belongs_to` association.
109
+
110
+ ```
111
+ module MyApi
112
+ class Account < JsonApiClient::Resource
113
+ belongs_to :user
114
+ end
115
+ end
116
+
117
+
118
+ ```
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'JsonApiClient'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,18 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ class BaseAssociation
4
+ attr_reader :attr_name, :klass, :options
5
+ def initialize(attr_name, klass, options = {})
6
+ @attr_name = attr_name
7
+ @klass = klass
8
+ @options = options
9
+ end
10
+
11
+ def association_class
12
+ @association_class ||= Utils.compute_type(klass, options.fetch(:class_name) do
13
+ attr_name.to_s.classify
14
+ end)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ module BelongsTo
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def belongs_to(attr_name, options = {})
8
+ # self.associations = self.associations + [HasOne::Association.new(attr_name, self, options)]
9
+ self.associations += [BelongsTo::Association.new(attr_name, self, options)]
10
+ end
11
+ end
12
+
13
+ class Association < BaseAssociation
14
+ def parse(params)
15
+ params ? association_class.new(params) : nil
16
+ end
17
+
18
+ def param
19
+ :"#{attr_name}_id"
20
+ end
21
+
22
+ def to_prefix_path
23
+ "#{attr_name.to_s.pluralize}/%{#{param}}"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ module HasMany
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def has_many(attr_name, options = {})
8
+ self.associations = self.associations + [HasMany::Association.new(attr_name, self, options)]
9
+ end
10
+ end
11
+
12
+ class Association < BaseAssociation
13
+ def parse(param)
14
+ [param].flatten.map{|data| association_class.new(data) }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ module HasOne
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def has_one(attr_name, options = {})
8
+ # self.associations = self.associations + [HasOne::Association.new(attr_name, self, options)]
9
+ self.associations += [HasOne::Association.new(attr_name, self, options)]
10
+ end
11
+ end
12
+
13
+ class Association < BaseAssociation
14
+ def parse(params)
15
+ params ? association_class.new(params) : nil
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,63 @@
1
+ module JsonApiClient
2
+ module Associations
3
+ autoload :BaseAssociation, 'json_api_client/associations/base_association'
4
+ autoload :BelongsTo, 'json_api_client/associations/belongs_to'
5
+ autoload :HasMany, 'json_api_client/associations/has_many'
6
+ autoload :HasOne, 'json_api_client/associations/has_one'
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ class_attribute :associations
12
+ self.associations = []
13
+
14
+ include BelongsTo
15
+ include HasMany
16
+ include HasOne
17
+
18
+ initialize :load_associations
19
+ end
20
+
21
+ module ClassMethods
22
+ def belongs_to_associations
23
+ associations.select{|association| association.is_a?(BelongsTo::Association) }
24
+ end
25
+
26
+ def prefix_params
27
+ belongs_to_associations.map(&:param)
28
+ end
29
+
30
+ def prefix_path
31
+ belongs_to_associations.map(&:to_prefix_path).join("/")
32
+ end
33
+
34
+ def path(params = nil)
35
+ parts = [table_name]
36
+ if params
37
+ slurp = params.slice(*prefix_params)
38
+ prefix_params.each do |param|
39
+ params.delete(param)
40
+ end
41
+ parts.unshift(prefix_path % slurp)
42
+ else
43
+ parts.unshift(prefix_path)
44
+ end
45
+ parts.reject!{|part| part == "" }
46
+ File.join(*parts)
47
+ rescue KeyError
48
+ raise ArgumentError, "Not all prefix parameters specified"
49
+ end
50
+ end
51
+
52
+ protected
53
+
54
+ def load_associations(params)
55
+ associations.each do |association|
56
+ if params.has_key?(association.attr_name.to_s)
57
+ set_attribute(association.attr_name, association.parse(params[association.attr_name.to_s]))
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ module JsonApiClient
2
+ module Attributes
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attr_reader :attributes
7
+ initialize do |obj, params|
8
+ obj.attributes = params
9
+ end
10
+ end
11
+
12
+ def attributes=(attrs = {})
13
+ @attributes ||= {}.with_indifferent_access
14
+ @attributes.merge!(attrs)
15
+ end
16
+
17
+ def update_attributes(attrs = {})
18
+ self.attributes = attrs
19
+ save
20
+ end
21
+
22
+ def method_missing(method, *args, &block)
23
+ if match = method.to_s.match(/^(.*)=$/)
24
+ set_attribute(match[1], args.first)
25
+ elsif has_attribute?(method)
26
+ attributes[method]
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ def persisted?
33
+ attributes.has_key?(primary_key)
34
+ end
35
+
36
+ def query_params
37
+ attributes.except(primary_key)
38
+ end
39
+
40
+ def to_param
41
+ attributes.fetch(primary_key, "").to_s
42
+ end
43
+
44
+ protected
45
+
46
+ def read_attribute(name)
47
+ attributes.fetch(name, nil)
48
+ end
49
+
50
+ def set_attribute(name, value)
51
+ attributes[name] = value
52
+ end
53
+
54
+ def has_attribute?(attr_name)
55
+ attributes.has_key?(attr_name)
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,32 @@
1
+ module JsonApiClient
2
+ class Connection
3
+
4
+ attr_reader :faraday
5
+
6
+ def initialize(site)
7
+ @faraday = Faraday.new(site) do |builder|
8
+ builder.request :url_encoded
9
+ builder.use Middleware::JsonRequest
10
+ builder.use Middleware::Status
11
+ builder.use FaradayMiddleware::ParseJson, content_type: /\bjson$/
12
+ builder.adapter Faraday.default_adapter
13
+ end
14
+ yield(self) if block_given?
15
+ end
16
+
17
+ # insert middleware before ParseJson - middleware executed in reverse order -
18
+ # inserted middleware will run after json parsed
19
+ def use(middleware, *args, &block)
20
+ faraday.builder.insert_before(FaradayMiddleware::ParseJson, middleware, *args, &block)
21
+ end
22
+
23
+ def delete(middleware)
24
+ faraday.builder.delete(middleware)
25
+ end
26
+
27
+ def execute(query)
28
+ faraday.send(query.request_method, query.path, query.params, query.headers)
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ module JsonApiClient
2
+ module Errors
3
+ class ApiError < Exception
4
+ attr_reader :env
5
+ def initialize(env)
6
+ @env = env
7
+ end
8
+ end
9
+
10
+ class ClientError < ApiError
11
+ end
12
+
13
+ class ServerError < ApiError
14
+ def message
15
+ "Internal server error"
16
+ end
17
+ end
18
+
19
+ class NotFound < ServerError
20
+ attr_reader :uri
21
+ def initialize(uri)
22
+ @uri = uri
23
+ end
24
+ def message
25
+ "Couldn't find resource at: #{uri.to_s}"
26
+ end
27
+ end
28
+
29
+ class UnexpectedStatus < ServerError
30
+ attr_reader :code, :uri
31
+ def initialize(code, uri)
32
+ @code = code
33
+ @uri = uri
34
+ end
35
+ def message
36
+ "Unexpected response status: #{code} from: #{uri.to_s}"
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ module JsonApiClient
2
+ module Links
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attr_accessor :links
7
+
8
+ initialize do |obj, params|
9
+ links = params.delete(:links)
10
+ obj.links = links if links
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module JsonApiClient
2
+ module Middleware
3
+ class JsonRequest < Faraday::Middleware
4
+ def call(environment)
5
+ environment[:request_headers]["Accept"] = "application/json,*/*"
6
+ uri = environment[:url]
7
+ uri.path = uri.path + ".json" unless uri.path.match(/\.json$/)
8
+ @app.call(environment)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ module JsonApiClient
2
+ module Middleware
3
+ class Status < Faraday::Middleware
4
+ def call(environment)
5
+ @app.call(environment).on_complete do |env|
6
+ handle_status(env[:status], env)
7
+
8
+ # look for meta[:status]
9
+ if env[:body].is_a?(Hash)
10
+ code = env[:body].fetch("meta", {}).fetch("status", 200).to_i
11
+ handle_status(code, env)
12
+ end
13
+ end
14
+ end
15
+
16
+ protected
17
+
18
+ def handle_status(code, env)
19
+ case code
20
+ when 404
21
+ raise Errors::NotFound, env[:uri]
22
+ when 500..599
23
+ raise Errors::ServerError, env
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ module JsonApiClient
2
+ module Middleware
3
+ autoload :JsonRequest, 'json_api_client/middleware/json_request'
4
+ autoload :Status, 'json_api_client/middleware/status'
5
+ end
6
+ end
@@ -0,0 +1,42 @@
1
+ module JsonApiClient
2
+ class Parser
3
+
4
+ class << self
5
+ def parse(klass, response)
6
+ data = response.body
7
+ ResultSet.build(klass, data) do |result_set|
8
+ handle_pagination(result_set, data)
9
+ handle_errors(result_set, data)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def handle_pagination(result_set, data)
16
+ meta = data.fetch("meta", {})
17
+ result_set.per_page = meta.fetch("per_page") do
18
+ result_set.length
19
+ end
20
+ result_set.total_entries = meta.fetch("total_entries") do
21
+ result_set.length
22
+ end
23
+ result_set.current_page = meta.fetch("current_page", 1)
24
+
25
+ # can fall back to calculating via total entries and per_page
26
+ result_set.total_pages = meta.fetch("total_pages") do
27
+ (1.0 * result_set.total_entries / result_set.per_page).ceil rescue 1
28
+ end
29
+
30
+ # can fall back to calculating via per_page and current_page
31
+ result_set.offset = meta.fetch("offset") do
32
+ result_set.per_page * (result_set.current_page - 1)
33
+ end
34
+ end
35
+
36
+ def handle_errors(result_set, data)
37
+ result_set.errors = data.fetch("meta", {}).fetch("errors", [])
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ module JsonApiClient
2
+ module Query
3
+ class Base
4
+ class_attribute :request_method
5
+ attr_reader :klass, :headers, :path, :params
6
+
7
+ def initialize(klass, args)
8
+ @klass = klass
9
+ build_params(args)
10
+ @headers = klass.default_headers.dup
11
+
12
+ @path = begin
13
+ p = klass.path(@params)
14
+ if @params.has_key?(klass.primary_key) && !@params[klass.primary_key].is_a?(Array)
15
+ p = File.join(p, @params.delete(klass.primary_key).to_s)
16
+ end
17
+ p
18
+ end
19
+ end
20
+
21
+ def build_params(args)
22
+ @params = args
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module JsonApiClient
2
+ module Query
3
+ class Create < Base
4
+ self.request_method = :post
5
+
6
+ def build_params(args)
7
+ @params = {klass.resource_name => args.except(klass.primary_key)}
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module JsonApiClient
2
+ module Query
3
+ class Destroy < Base
4
+ self.request_method = :delete
5
+
6
+ def params
7
+ nil
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ module JsonApiClient
2
+ module Query
3
+ class Find < Base
4
+ self.request_method = :get
5
+
6
+ def build_params(args)
7
+ @params = case args
8
+ when Hash
9
+ args
10
+ when Array
11
+ {klass.primary_key.to_s.pluralize.to_sym => args.join(",")}
12
+ else
13
+ {klass.primary_key => args}
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module JsonApiClient
2
+ module Query
3
+ class Update < Base
4
+ self.request_method = :put
5
+
6
+ def build_params(args)
7
+ @params = {klass.primary_key => args.delete(klass.primary_key), klass.resource_name => args}
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module JsonApiClient
2
+ module Query
3
+ autoload :Base, 'json_api_client/query/base'
4
+ autoload :Create, 'json_api_client/query/create'
5
+ autoload :Destroy, 'json_api_client/query/destroy'
6
+ autoload :Find, 'json_api_client/query/find'
7
+ autoload :Update, 'json_api_client/query/update'
8
+ end
9
+ end
@@ -0,0 +1,119 @@
1
+ require 'forwardable'
2
+ require 'active_support/concern'
3
+ require 'active_support/inflector'
4
+ require 'active_support/core_ext/hash'
5
+ require 'active_support/core_ext/class/attribute'
6
+
7
+ module JsonApiClient
8
+ class Resource
9
+ class_attribute :site, :primary_key, :link_style, :default_headers, :initializers, :parser
10
+
11
+ self.primary_key = :id
12
+ self.link_style = :id # or :url
13
+ self.default_headers = {}
14
+ self.initializers = []
15
+ self.parser = Parser
16
+
17
+ class << self
18
+ # first 'scope' should build a new scope object
19
+ extend Forwardable
20
+ def_delegators :new_scope, :where, :order, :includes, :all, :paginate, :page, :first
21
+
22
+ # base URL for this resource
23
+ def resource
24
+ File.join(site, path)
25
+ end
26
+
27
+ def table_name
28
+ resource_name.pluralize
29
+ end
30
+
31
+ def resource_name
32
+ name.demodulize.underscore
33
+ end
34
+
35
+ def find(conditions)
36
+ run_request(Query::Find.new(self, conditions))
37
+ end
38
+
39
+ def create(conditions = {})
40
+ result = run_request(Query::Create.new(self, conditions))
41
+ return nil if result.errors.length > 0
42
+ result.first
43
+ end
44
+
45
+ def connection
46
+ @connection ||= begin
47
+ super
48
+ rescue
49
+ build_connection
50
+ end
51
+ yield(@connection) if block_given?
52
+ @connection
53
+ end
54
+
55
+ def run_request(query)
56
+ parse(connection.execute(query))
57
+ end
58
+
59
+ private
60
+
61
+ def new_scope
62
+ Scope.new(self)
63
+ end
64
+
65
+ def parse(data)
66
+ parser.parse(self, data)
67
+ end
68
+
69
+ def build_connection
70
+ Connection.new(site)
71
+ end
72
+
73
+ def initialize(method = nil, &block)
74
+ self.initializers.push(method || block)
75
+ end
76
+ end
77
+
78
+ include Attributes
79
+ include Associations
80
+ include Links
81
+
82
+ attr_accessor :errors
83
+ def initialize(params = {})
84
+ initializers.each do |initializer|
85
+ if initializer.respond_to?(:call)
86
+ initializer.call(self, params)
87
+ else
88
+ self.send(initializer, params)
89
+ end
90
+ end
91
+ end
92
+
93
+ def save
94
+ query = persisted? ?
95
+ Query::Update.new(self.class, attributes) :
96
+ Query::Create.new(self.class, attributes)
97
+
98
+ run_request(query)
99
+ end
100
+
101
+ def destroy
102
+ run_request(Query::Destroy.new(self.class, attributes))
103
+ end
104
+
105
+ protected
106
+
107
+ def run_request(query)
108
+ response = self.class.run_request(query)
109
+ self.errors = response.errors
110
+ if updated = response.first
111
+ self.attributes = updated.attributes
112
+ else
113
+ self.attributes = {}
114
+ end
115
+ return errors.length == 0
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,13 @@
1
+ module JsonApiClient
2
+ class ResultSet < Array
3
+
4
+ attr_accessor :total_pages, :total_entries, :offset, :per_page, :current_page, :errors
5
+
6
+ def self.build(klass, data)
7
+ result_data = data.fetch(klass.table_name, [])
8
+ new(result_data.map {|attributes| klass.new(attributes) }).tap do |result_set|
9
+ yield(result_set) if block_given?
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,48 @@
1
+ module JsonApiClient
2
+ class Scope
3
+ attr_reader :klass, :params
4
+
5
+ def initialize(klass)
6
+ @klass = klass
7
+ @params = {}
8
+ end
9
+
10
+ def where(conditions = {})
11
+ @params.merge!(conditions)
12
+ self
13
+ end
14
+ alias paginate where
15
+
16
+ def order(conditions)
17
+ where(order: conditions)
18
+ end
19
+
20
+ def includes(*tables)
21
+ @params[:includes] ||= []
22
+ @params[:includes] += tables.flatten
23
+ self
24
+ end
25
+
26
+ def page(number)
27
+ where(page: number)
28
+ end
29
+
30
+ def first
31
+ paginate(page: 1, per_page: 1).to_a.first
32
+ end
33
+
34
+ def build
35
+ klass.new(params)
36
+ end
37
+
38
+ def to_a
39
+ @to_a ||= klass.find(params)
40
+ end
41
+ alias all to_a
42
+
43
+ def method_missing(method_name, *args, &block)
44
+ to_a.send(method_name, *args, &block)
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ module JsonApiClient
2
+ module Utils
3
+
4
+ def self.compute_type(klass, type_name)
5
+ # If the type is prefixed with a scope operator then we assume that
6
+ # the type_name is an absolute reference.
7
+ return type_name.constantize if type_name.match(/^::/)
8
+
9
+ # Build a list of candidates to search for
10
+ candidates = []
11
+ klass.name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
12
+ candidates << type_name
13
+
14
+ candidates.each do |candidate|
15
+ begin
16
+ constant = candidate.constantize
17
+ return constant if candidate == constant.to_s
18
+ rescue NameError => e
19
+ # We don't want to swallow NoMethodError < NameError errors
20
+ raise e unless e.instance_of?(NameError)
21
+ end
22
+ end
23
+
24
+ raise NameError, "uninitialized constant #{candidates.first}"
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module JsonApiClient
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,18 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'json'
4
+
5
+ module JsonApiClient
6
+ autoload :Associations, 'json_api_client/associations'
7
+ autoload :Attributes, 'json_api_client/attributes'
8
+ autoload :Connection, 'json_api_client/connection'
9
+ autoload :Errors, 'json_api_client/errors'
10
+ autoload :Links, 'json_api_client/links'
11
+ autoload :Middleware, 'json_api_client/middleware'
12
+ autoload :Parser, 'json_api_client/parser'
13
+ autoload :Query, 'json_api_client/query'
14
+ autoload :Resource, 'json_api_client/resource'
15
+ autoload :ResultSet, 'json_api_client/result_set'
16
+ autoload :Scope, 'json_api_client/scope'
17
+ autoload :Utils, 'json_api_client/utils'
18
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_api_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Jeff Ching
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.8.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.8.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday_middleware
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 0.9.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Build client libraries compliant with specification defined by jsonapi.org
70
+ email: ching.jeff@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/json_api_client/associations/base_association.rb
76
+ - lib/json_api_client/associations/belongs_to.rb
77
+ - lib/json_api_client/associations/has_many.rb
78
+ - lib/json_api_client/associations/has_one.rb
79
+ - lib/json_api_client/associations.rb
80
+ - lib/json_api_client/attributes.rb
81
+ - lib/json_api_client/connection.rb
82
+ - lib/json_api_client/errors.rb
83
+ - lib/json_api_client/links.rb
84
+ - lib/json_api_client/middleware/json_request.rb
85
+ - lib/json_api_client/middleware/status.rb
86
+ - lib/json_api_client/middleware.rb
87
+ - lib/json_api_client/parser.rb
88
+ - lib/json_api_client/query/base.rb
89
+ - lib/json_api_client/query/create.rb
90
+ - lib/json_api_client/query/destroy.rb
91
+ - lib/json_api_client/query/find.rb
92
+ - lib/json_api_client/query/update.rb
93
+ - lib/json_api_client/query.rb
94
+ - lib/json_api_client/resource.rb
95
+ - lib/json_api_client/result_set.rb
96
+ - lib/json_api_client/scope.rb
97
+ - lib/json_api_client/utils.rb
98
+ - lib/json_api_client/version.rb
99
+ - lib/json_api_client.rb
100
+ - LICENSE
101
+ - Rakefile
102
+ - README.md
103
+ homepage: http://github.com/chingor13/json_api_client
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.0.3
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Build client libraries compliant with specification defined by jsonapi.org
127
+ test_files: []