jeckle 0.4.0.beta3 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: daea79214eac076d0f45217ccad97e299f0b8ed4
4
- data.tar.gz: 815eb79a5058729ef025476a438cff1a5fc358c4
2
+ SHA256:
3
+ metadata.gz: 39ce2c6f4ee95e7aa03d9b3b40ab8de60422e947cf4cc7cd7709c669c1f71a68
4
+ data.tar.gz: 138d161339af0ef6b9da3ba0a3990376e1b1255dafb17a1ab385ddfc528a50c4
5
5
  SHA512:
6
- metadata.gz: d7299c4462181501c834c7b33cfde8d06beca7ad21611c87a2b173070e2e9f864b3f01977045ee914cb54efe71c8c913e6fdce6021669c37735303144688ef17
7
- data.tar.gz: 83388f41b76f585a801c13d7f4742237448f6982c44a5210abc5c0e3f37ba1a6172d74aa65dc1eefb82f0a7e3002337536c89961c309f0a0b78134efd31aff44
6
+ metadata.gz: 0fa2bbe9ad7f9119d4fe2aad99494e807c4e75897db327a2ad3ab717fab62dafbb1e777a1b3edc9e501adb1aebac5c44f9da310cd86dbfacb244783ccb9dc8ed
7
+ data.tar.gz: cd7eceaa6bca85d8147b546c405bf1f4b6545284c564b08ba0c1e34868f5cfdfb20417ddc9c3b07f65566874613c9ee7bb29ce2a870dad655a95b3a218619df4
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Jeckle
2
2
 
3
- [![Build Status](https://travis-ci.org/tomas-stefano/jeckle.svg?branch=master)](https://travis-ci.org/tomas-stefano/jeckle)
4
- [![Code Climate](https://codeclimate.com/github/tomas-stefano/jeckle.png)](https://codeclimate.com/github/tomas-stefano/jeckle)
5
- [![Test Coverage](https://codeclimate.com/github/tomas-stefano/jeckle/coverage.png)](https://codeclimate.com/github/tomas-stefano/jeckle)
3
+ [![CI](https://github.com/tomas-stefano/jeckle/actions/workflows/ci.yml/badge.svg)](https://github.com/tomas-stefano/jeckle/actions/workflows/ci.yml)
6
4
 
7
5
  Wrap APIs with easiness and flexibility.
8
6
 
@@ -20,53 +18,105 @@ Let third party APIs be Heckle for your app's Jeckle.
20
18
 
21
19
  Add this line to your application's Gemfile:
22
20
 
23
- gem 'jeckle'
21
+ ```ruby
22
+ gem 'jeckle'
23
+ ```
24
24
 
25
25
  And then execute:
26
26
 
27
- $ bundle
27
+ ```sh
28
+ $ bundle
29
+ ```
28
30
 
29
- ### For Rails applications
31
+ ## Usage
30
32
 
31
- We recommend to create a initializer:
33
+ ### Configuring an API
32
34
 
33
- ```ruby
34
- # config/initializers/jeckle.rb
35
+ Let's say you'd like to connect your app to Dribbble.com - a community of designers sharing screenshots of their work, process, and projects.
36
+
37
+ First, you would need to configure the API:
35
38
 
39
+ ```ruby
36
40
  Jeckle.configure do |config|
37
- config.register :some_service do |api|
38
- api.base_uri = 'http://api.someservice.com'
39
- api.headers = {
40
- 'Accept' => 'application/json'
41
- }
42
- api.namespaces = { prefix: 'api', version: 'v1' }
43
- api.logger = Rails.logger
44
- api.read_timeout = 5
41
+ config.register :dribbble do |api|
42
+ api.base_uri = 'http://api.dribbble.com'
43
+ api.middlewares do
44
+ response :json
45
+ end
45
46
  end
46
47
  end
47
48
  ```
48
49
 
49
- And then put your API stuff scoped inside a `services` folder:
50
+ ### Mapping resources
50
51
 
51
- ```ruby
52
- # app/services/some_service/models/my_resource.rb
52
+ Following the previous example, Dribbble.com consists of pieces of web designers work called "Shots". Each shot has the attributes `id`, `name`, `url` and `image_url`. A Jeckle resource representing Dribbble's shots would be something like this:
53
53
 
54
- module SomeService
55
- module Models
56
- class MyResource
57
- include Jeckle::Resource
54
+ ```ruby
55
+ class Shot
56
+ include Jeckle::Resource
58
57
 
59
- api :some_service
58
+ api :dribbble
60
59
 
61
- attribute :id
62
- end
63
- end
60
+ attribute :id, Integer
61
+ attribute :name, String
62
+ attribute :url, String
63
+ attribute :image_url, String
64
64
  end
65
65
  ```
66
66
 
67
+ ### Fetching data
68
+
69
+ The resource class allows us to search shots through HTTP requests to the API, based on the provided information. For example, we can find a specific shot by providing its id to the `find` method:
70
+
71
+ ```ruby
72
+ # GET http://api.dribbble.com/shots/1600459
73
+ shot = Shot.find 1600459
74
+ ```
75
+
76
+ That will return a `Shot` instance, containing the shot info:
77
+
78
+ ```ruby
79
+ shot.id
80
+ => 1600459
81
+
82
+ shot.name
83
+ => "Daryl Heckle And Jeckle Oates"
84
+
85
+ shot.image_url
86
+ => "https://d13yacurqjgara.cloudfront.net/users/85699/screenshots/1600459/daryl_heckle_and_jeckle_oates-dribble.jpg"
87
+ ```
88
+
89
+ You can also look for many shots matching one or more attributes, by using the `search` method:
90
+
91
+ ```ruby
92
+ # GET http://api.dribbble.com/shots?name=avengers
93
+ shots = Shot.search name: 'avengers'
94
+ ```
95
+
96
+ ### Attribute Aliasing
97
+
98
+ Sometimes you want to call the API's attributes something else, either because their names aren't very concise or because they're out of you app's convention. If that's the case, you can add an `as` option:
99
+
100
+ ```ruby
101
+ attribute :thumbnailSize, String, as: :thumbnail_size
102
+ ```
103
+
104
+ Both mapping will work:
105
+
106
+ ```ruby
107
+ shot.thumbnailSize
108
+ => "50x50"
109
+
110
+ shot.thumbnail_size
111
+ => "50x50"
112
+ ```
113
+
114
+ We're all set! Now we can expand the mapping of our API, e.g to add ability to search Dribbble Designer directory by adding Designer class, or we can expand the original mapping of Shot class to include more attributes, such as tags or comments.
115
+
116
+ ## Examples
117
+
118
+ You can see more examples here: [https://github.com/tomas-stefano/jeckle/tree/master/examples](https://github.com/tomas-stefano/jeckle/tree/master/examples)
119
+
67
120
  ## Roadmap
68
121
 
69
- - Faraday middleware abstraction
70
- - Per action API
71
- - Comprehensive restful actions
72
- - Testability
122
+ Follow [GitHub's milestones](https://github.com/tomas-stefano/jeckle/milestones)
data/lib/jeckle/api.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jeckle
2
4
  class API
3
5
  attr_accessor :logger
@@ -10,14 +12,14 @@ module Jeckle
10
12
  conn.params = params
11
13
  conn.response :logger, logger
12
14
 
13
- conn.basic_auth basic_auth[:username], basic_auth[:password] if basic_auth
14
- conn.instance_exec &@middlewares_block if @middlewares_block
15
+ conn.request :authorization, :basic, basic_auth[:username], basic_auth[:password] if basic_auth
16
+ conn.instance_exec(&@middlewares_block) if @middlewares_block
15
17
  end
16
18
  end
17
19
 
18
20
  def basic_auth=(credential_params)
19
- [:username, :password].all? do |key|
20
- credential_params.has_key? key
21
+ %i[username password].all? do |key|
22
+ credential_params.key? key
21
23
  end or raise Jeckle::NoUsernameOrPasswordError, credential_params
22
24
 
23
25
  @basic_auth = credential_params
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jeckle
4
+ module AttributeAliasing
5
+ def attribute(name, coercion, options = {})
6
+ if (custom_name = options.delete(:as))
7
+ super(custom_name, coercion, options)
8
+
9
+ alias_method name, custom_name
10
+ alias_method :"#{name}=", :"#{custom_name}="
11
+ else
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/jeckle/errors.rb CHANGED
@@ -1,29 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jeckle
2
4
  class ArgumentError < ::ArgumentError; end
3
5
 
4
6
  class NoSuchAPIError < ArgumentError
5
7
  def initialize(api)
6
- message = %{The API name '#{api}' doesn't exist in Jeckle definitions.
8
+ message = %(The API name '#{api}' doesn't exist in Jeckle definitions.
7
9
 
8
10
  Heckle: - Hey chum, what we can do now?
9
11
  Jeckle: - Old chap, you need to put the right API name!
10
12
  Heckle: - Hey pal, tell me the APIs then!
11
13
  Jeckle: - Deal the trays, old thing: #{Jeckle::Setup.registered_apis.keys}.
12
- }
14
+ )
13
15
 
14
- super message
16
+ super(message)
15
17
  end
16
18
  end
17
19
 
18
20
  class NoUsernameOrPasswordError < ArgumentError
19
- def initialize(credentials)
20
- message = %{No such keys "username" and "password" on `basic_auth` definition"
21
+ def initialize(_credentials)
22
+ message = %(No such keys "username" and "password" on `basic_auth` definition"
21
23
 
22
24
  Heckle: - Hey chum, what we can do now?
23
25
  Jeckle: - Old chap, you need to define a username and a password for basic auth!
24
- }
26
+ )
25
27
 
26
- super message
28
+ super(message)
27
29
  end
28
30
  end
29
31
  end
data/lib/jeckle/http.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jeckle
2
4
  module HTTP
3
5
  def self.included(base)
@@ -11,8 +13,6 @@ module Jeckle
11
13
  end
12
14
  end
13
15
 
14
- # @public
15
- #
16
16
  # The name of the resource that Jeckle uses to make the request
17
17
  #
18
18
  # @example
@@ -41,23 +41,6 @@ module Jeckle
41
41
  @resource_name ||= model_name.element.pluralize
42
42
  end
43
43
 
44
- # @public
45
- #
46
- # Overwritten the resource name without write the resource name method.
47
- #
48
- # @example
49
- #
50
- # module OtherApi
51
- # class Project
52
- # include Jeckle::Resource
53
- # resource 'projects.json'
54
- # end
55
- # end
56
- #
57
- def resource(jeckle_resource_name)
58
- @resource_name = jeckle_resource_name
59
- end
60
-
61
44
  # The API name that Jeckle uses to find all the api settings like domain, headers, etc.
62
45
  #
63
46
  # @example
@@ -75,14 +58,14 @@ module Jeckle
75
58
  #
76
59
  def api(registered_api_name)
77
60
  api_mapping[:default_api] = Jeckle::Setup.registered_apis.fetch(registered_api_name)
78
- rescue KeyError => e
61
+ rescue KeyError
79
62
  raise Jeckle::NoSuchAPIError, registered_api_name
80
63
  end
81
64
 
82
65
  # @deprecated Please use {#api} instead
83
66
  #
84
67
  def default_api(registered_api_name)
85
- warn "[DEPRECATION] `default_api` is deprecated. Please use `api` instead."
68
+ warn '[DEPRECATION] `default_api` is deprecated. Please use `api` instead.'
86
69
  api(registered_api_name)
87
70
  end
88
71
 
@@ -91,14 +74,8 @@ module Jeckle
91
74
  end
92
75
 
93
76
  def run_request(endpoint, options = {})
94
- request = Jeckle::Request.run api_mapping[:default_api], endpoint, options
95
-
96
- if logger = api_mapping[:default_api].logger
97
- logger.debug("#{self} Response: #{request.response.body}")
98
- end
99
-
100
- request
77
+ Jeckle::Request.run api_mapping[:default_api], endpoint, options
101
78
  end
102
79
  end
103
80
  end
104
- end
81
+ end
data/lib/jeckle/model.rb CHANGED
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jeckle
2
4
  module Model
3
5
  def self.included(base)
4
- base.send :include, ActiveModel::Validations
5
- base.send :include, Virtus.model
6
+ base.include ActiveModel::Validations
7
+ base.include Virtus.model
6
8
  end
7
9
  end
8
10
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jeckle
2
4
  class Request
3
5
  attr_reader :api, :body, :headers, :method, :params, :response, :endpoint
@@ -6,17 +8,28 @@ module Jeckle
6
8
  @api = api
7
9
 
8
10
  @method = options.delete(:method) || :get
9
- @body = options.delete(:body) if %w(post put).include?(method.to_s)
11
+ @body = options.delete(:body) if %w[post put patch].include?(method.to_s)
10
12
  @headers = options.delete(:headers)
11
13
 
14
+ if options[:params].nil? && options.size.positive?
15
+ warn %([DEPRECATION] Sending URL params mixed with options hash is deprecated.
16
+ Instead of doing this:
17
+ run_request 'cars/search', id: id, method: :get
18
+ Do this:
19
+ run_request 'cars/search', params: { id: id }, method: :get)
20
+
21
+ @params = options
22
+ else
23
+ @params = options.delete(:params) || {}
24
+ end
25
+
12
26
  @endpoint = endpoint
13
- @params = options
14
27
 
15
28
  @response = perform_api_request
16
29
  end
17
30
 
18
- def self.run(*args)
19
- new *args
31
+ def self.run(api, endpoint, options = {})
32
+ new(api, endpoint, options)
20
33
  end
21
34
 
22
35
  private
@@ -1,11 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jeckle
2
4
  module Resource
3
5
  def self.included(base)
4
- base.send :include, ActiveModel::Naming
6
+ base.include ActiveModel::Naming
7
+
8
+ base.include Jeckle::Model
9
+ base.include Jeckle::HTTP
10
+ base.include Jeckle::RESTActions
5
11
 
6
- base.send :include, Jeckle::Model
7
- base.send :include, Jeckle::HTTP
8
- base.send :include, Jeckle::RESTActions
12
+ base.extend Jeckle::AttributeAliasing
9
13
  end
10
14
  end
11
15
  end
@@ -1,106 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jeckle
2
4
  module RESTActions
3
5
  def self.included(base)
4
- base.send :extend, Jeckle::RESTActions::Collection
6
+ base.extend Jeckle::RESTActions::Collection
5
7
  end
6
8
 
7
9
  module Collection
8
- # @public
9
- #
10
- # The root name that Jeckle will parse the <b>response</b>. Default is <b>false</b>.
11
- #
12
- # @example
13
- #
14
- # module Dribble
15
- # class Shot
16
- # include Jeckle::Resource
17
- # root collection: true, member: true
18
- # end
19
- # end
20
- #
21
- # Shot.collection_root_name # => Will parse the root node as 'shots'
22
- # Shot.member_root_name # => Will parse the root node as 'shot'
23
- #
24
- # Sometimes that are APIs you need to fetch /projects, BUT the root node is extremely different from the resource name.
25
- #
26
- # module OtherApi
27
- # class Project
28
- # include Jeckle::Resource
29
- # api :my_api
30
- # root collection: 'awesome-projects', member: 'awesome-project'
31
- # end
32
- # end
33
- #
34
- def root(options={})
35
- @collection_root_name = find_root_name(options[:collection], :pluralize)
36
- @member_root_name = find_root_name(options[:member], :singularize)
37
- end
38
-
39
- # @public
40
- #
41
- # Member action that requests for the resource using the resource name
42
- #
43
- # @example
44
- #
45
- # Post.find(1) # => posts/1
46
- #
47
10
  def find(id)
48
- endpoint = "#{resource_name}/#{id}"
49
- response = run_request(endpoint).response.body
50
- attributes = parse_response(response, member_root_name)
11
+ endpoint = "#{resource_name}/#{id}"
12
+ attributes = run_request(endpoint).response.body
51
13
 
52
- new(attributes)
14
+ new attributes
53
15
  end
54
16
 
55
- # @public
56
- #
57
- # Collection action that requests for the resource using the resource name
58
- #
59
- # @example
60
- #
61
- # Post.search({ status: 'published' }) # => posts/?status=published
62
- # Post.where({ status: 'published' }) # => posts/?status=published
63
- #
64
17
  def search(params = {})
65
- request = run_request(resource_name, params)
66
- response = request.response
67
- collection = parse_response(response.body, collection_root_name)
18
+ custom_resource_name = params.delete(:resource_name) if params.is_a?(Hash)
68
19
 
69
- CollectionResponse.new(collection, context: self, response: response)
70
- end
71
- alias :where :search
72
-
73
- # @private
74
- #
75
- def parse_response(response, root_name)
76
- if root_name
77
- response[root_name]
78
- else
79
- response
80
- end
81
- end
82
-
83
- # @private
84
- #
85
- def find_root_name(root_name, root_method)
86
- return root_name if root_name.is_a?(String)
87
-
88
- if root_name
89
- model_name.element.send(root_method)
90
- end
91
- end
92
-
93
- # @private
94
- #
95
- def collection_root_name
96
- @collection_root_name
97
- end
20
+ response = run_request(custom_resource_name || resource_name, params: params).response.body || []
21
+ collection = response.is_a?(Array) ? response : response[resource_name]
98
22
 
99
- # @private
100
- #
101
- def member_root_name
102
- @member_root_name
23
+ Array(collection).collect { |attrs| new attrs }
103
24
  end
104
25
  end
105
26
  end
106
- end
27
+ end
data/lib/jeckle/setup.rb CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jeckle
2
4
  class Setup
3
- # Register apis, providing all the configurations to it.
5
+ # Register APIs, providing all the configurations to it.
4
6
  #
5
7
  # @example
6
8
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Jeckle
2
- VERSION = '0.4.0.beta3'
4
+ VERSION = '0.4.0'
3
5
  end
data/lib/jeckle.rb CHANGED
@@ -1,14 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_model'
2
4
  require 'faraday'
3
- require 'faraday_middleware'
4
5
  require 'virtus'
5
6
 
6
7
  require 'jeckle/version'
7
8
 
8
- %w(
9
- setup api model request http rest_actions
10
- resource errors collection_response
11
- ).each do |file_name|
9
+ %w[setup api model request http rest_actions resource errors attribute_aliasing].each do |file_name|
12
10
  require "jeckle/#{file_name}"
13
11
  end
14
12