json_api_client 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []