braque 0.0.1

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: b7c808e05c6710b54706c5a3dfdcca8a36af7e41
4
+ data.tar.gz: d90bcc7ed6d6cb29677ea06b92a344a72eb99f5d
5
+ SHA512:
6
+ metadata.gz: f093efaacf01e01ad2028759609f21768efad162bea40d28e8e0617bbe278ff4338d05be7af922555dff9b96cdf5ccaf744cbe3d29ebf92d1cb35e83fc512d68
7
+ data.tar.gz: 12acb696429f1f62af2c8983a14b52da695cae0a8584b4626fcd4ac8348c1270379d6d5461e6f4c4d66f47bf5ff751c7fe9f8b7f2888ce276c20619f6cdf95ee
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Dylan Fareed
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Braque
2
+
3
+ Braque aims to provide a simple and familiar interface for setting up clients to interact with [Hypermedia (hal+json)](http://stateless.co/hal_specification.html) API services. It is a lightweight wrapper around [Hyperclient](https://github.com/codegram/hyperclient) and [ActiveAttr](https://github.com/cgriego/active_attr).
4
+
5
+ Braque is an early-stage and exploratory project.
6
+
7
+ ### Model setup
8
+
9
+ ```Braque::Model``` is ActiveSupport concern. You can use Braque::Model to map a remote resource to a class in your application. Do so by including Braque::Model in the class, defining the API service's root url and authentication details, and listing attributes which we expect from the API.
10
+
11
+ ```ruby
12
+ class Article
13
+ include Braque::Model
14
+ self.api_root = Rails.application.config_for(:article_service)['url']
15
+ self.api_token = Rails.application.config_for(:article_service)['token']
16
+
17
+ attribute :id
18
+ attribute :title
19
+ attribute :body
20
+ attribute :summary
21
+ attribute :created_at
22
+ attribute :updated_at
23
+ end
24
+ ```
25
+
26
+ In a Rails app, you can then use this model simply:
27
+
28
+ ```ruby
29
+ class ArticlesController < ApplicationController
30
+ before_filter :find_article, except: :index
31
+
32
+ def index
33
+ @articles = Article.list(page: params[:page], size: params[:size])
34
+ end
35
+
36
+ def new
37
+ end
38
+
39
+ def create
40
+ @article = Article.create params[:article]
41
+ redirect_to article_path(@article)
42
+ end
43
+
44
+ def show
45
+ end
46
+
47
+ def edit
48
+ end
49
+
50
+ def update
51
+ @article = @article.save params[:article]
52
+ redirect_to article_path(id: @article.id)
53
+ end
54
+
55
+ def destroy
56
+ @article.destroy
57
+ redirect_to articles_path
58
+ end
59
+
60
+ private
61
+
62
+ def find_article
63
+ @article = Article.find params[:id]
64
+ end
65
+ end
66
+
67
+ ```
@@ -0,0 +1,19 @@
1
+ module Braque
2
+ module Collection
3
+ class LinkedArray < Array
4
+ attr_reader :next_link
5
+ attr_reader :previous_link
6
+
7
+ def initialize(response = [], klass)
8
+ next_link = response._links.try(:next)
9
+ previous_link = response._links.try(:prev)
10
+ retrieved_items = []
11
+ response.each do |item|
12
+ retrieved_items << klass.new(item)
13
+ end
14
+ @retrieved_items, @next_link, @previous_link = retrieved_items, next_link, previous_link
15
+ super retrieved_items
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,61 @@
1
+ module Braque
2
+ module Model
3
+ extend ::ActiveSupport::Concern
4
+ include ::ActiveAttr::Model
5
+
6
+ included do
7
+ def to_param
8
+ "#{id}"
9
+ end
10
+
11
+ def save(params = {})
12
+ response = self.class.client.method(self.class.instance_method_name).call(id: id)._patch("#{self.class.instance_method_name}" => params)
13
+ self.class.new response
14
+ end
15
+
16
+ def destroy
17
+ response = self.class.client.method(self.class.instance_method_name).call(id: id)._delete
18
+ end
19
+
20
+ class_eval <<-WRITER
21
+ def self.#{collection_method_name}
22
+ end
23
+ WRITER
24
+ end
25
+
26
+ module ClassMethods
27
+ include Braque::Collection
28
+ cattr_accessor :api_root
29
+ cattr_accessor :api_token
30
+
31
+ def list(options = {})
32
+ response = client.method(collection_method_name).call(page: options[:page], size: options[:size])
33
+ LinkedArray.new response, self
34
+ end
35
+
36
+ def find(id)
37
+ response = client.method(instance_method_name).call(id: id)
38
+ new response
39
+ end
40
+
41
+ def create(params = {})
42
+ response = client.method(collection_method_name).call._post("#{instance_method_name}" => params)
43
+ new response
44
+ end
45
+
46
+ def collection_method_name
47
+ name.tableize
48
+ end
49
+
50
+ def instance_method_name
51
+ collection_method_name.singularize
52
+ end
53
+
54
+ def client
55
+ Hyperclient.new(api_root) do |client|
56
+ client.headers['Http-Authorization'] = api_token
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,3 @@
1
+ module Braque
2
+ VERSION = '0.0.1'
3
+ end
data/lib/braque.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'active_support'
2
+ require 'active_attr'
3
+ require 'hyperclient'
4
+ require 'braque/version'
5
+ require 'braque/collection'
6
+ require 'braque/model'
7
+
8
+ module Braque
9
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Braque::Collection do
4
+ context 'paginate_and_sort in a GravityModel class' do
5
+ before(:all) do
6
+ class BraqueClass
7
+ include ::ActiveAttr::Model
8
+ include Braque::Collection
9
+ attribute :id
10
+ attribute :title
11
+ end
12
+
13
+ class ResponseLinks
14
+ attr_reader :next
15
+ attr_reader :prev
16
+ def initialize(next_str, previous_str)
17
+ @next = next_str
18
+ @prev = previous_str
19
+ end
20
+ end
21
+
22
+ class ResponseCollection < Array
23
+ attr_reader :_links
24
+ def initialize(array = [], next_str, previous_str)
25
+ @_links = ResponseLinks.new next_str, previous_str
26
+ super array
27
+ end
28
+ end
29
+ end
30
+
31
+ context 'LinkedArray' do
32
+ context '#initialize' do
33
+ before(:all) do
34
+ @array = [{ id: 1, title: 'hello' }, { id: 2, title: 'goodbye' }]
35
+ @response = ResponseCollection.new @array, true, false
36
+ @linked_array = BraqueClass::LinkedArray.new(@response, BraqueClass)
37
+ end
38
+ it 'returns an array of the correct number of items' do
39
+ expect(@linked_array.count).to eq 2
40
+ end
41
+ it 'returns an array of new items based on the calling class' do
42
+ expect(@linked_array.first.id).to eq 1
43
+ expect(@linked_array.first.title).to eq @array.first[:title]
44
+ expect(@linked_array.first.class.name).to eq 'BraqueClass'
45
+
46
+ expect(@linked_array[1].id).to eq 2
47
+ expect(@linked_array[1].title).to eq @array[1][:title]
48
+ expect(@linked_array[1].class.name).to eq 'BraqueClass'
49
+ end
50
+ it 'responds to previous_link with the correct value' do
51
+ expect(@linked_array).to respond_to(:previous_link)
52
+ expect(@linked_array.previous_link).to eq false
53
+ end
54
+ it 'responds to next_link with the correct value' do
55
+ expect(@linked_array).to respond_to(:next_link)
56
+ expect(@linked_array.next_link).to eq true
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,159 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Braque::Model, type: :model do
4
+ before(:all) do
5
+ class Breeze
6
+ include ::Braque::Model
7
+ self.api_root = 'http://localhost:9292'
8
+ self.api_token = 'replace-me'
9
+ attribute :id
10
+ attribute :title
11
+ end
12
+ end
13
+
14
+ let(:root_response) { JSON.parse(File.read 'spec/fixtures/root.json') }
15
+ let(:collection_response) { JSON.parse(File.read 'spec/fixtures/collection.json') }
16
+ let(:resource_response) { JSON.parse(File.read 'spec/fixtures/resource.json') }
17
+ let(:root_request) do
18
+ WebMock.stub_request(:get, "#{Breeze.api_root}/")
19
+ .with(headers: { 'Http-Authorization' => Breeze.api_token })
20
+ .to_return(status: 200, body: root_response)
21
+ end
22
+
23
+ context 'class methods' do
24
+ it 'responds to find, create, and list' do
25
+ expect(Breeze).to respond_to(:find, :create, :list)
26
+ end
27
+
28
+ it 'converts the class name into collection_method_name' do
29
+ expect(Breeze.collection_method_name).to eq 'breezes'
30
+ end
31
+
32
+ it 'converts the class name into instance_method_name' do
33
+ expect(Breeze.instance_method_name).to eq 'breeze'
34
+ end
35
+
36
+ context '.list' do
37
+ before(:each) do
38
+ root_request
39
+ @collection_request = WebMock.stub_request(:get, "#{Breeze.api_root}/breezes")
40
+ .with(headers: { 'Http-Authorization' => Breeze.api_token })
41
+ .to_return(status: 200, body: collection_response)
42
+ @breezes = Breeze.list
43
+ end
44
+ it 'performs the API root request' do
45
+ expect(root_request).to have_been_requested
46
+ end
47
+ it 'performs the collection request' do
48
+ expect(@collection_request).to have_been_requested
49
+ end
50
+ it 'returns an array of items' do
51
+ expect(@breezes.count).to be 1
52
+ expect(@breezes.first).to be_a_kind_of Breeze
53
+ end
54
+ it 'returns items with the correct class' do
55
+ expect(@breezes.first).to be_a_kind_of Breeze
56
+ end
57
+ it 'returns an array of items with the correct attributes' do
58
+ breeze = @breezes.first
59
+ expect(breeze.id).to eq collection_response['_embedded']['breezes'].first['id']
60
+ expect(breeze.title).to eq collection_response['_embedded']['breezes'].first['title']
61
+ end
62
+ end
63
+
64
+ context '.find' do
65
+ before(:each) do
66
+ root_request
67
+ @resource_request = WebMock.stub_request(:get, "#{Breeze.api_root}/breezes/1")
68
+ .with(headers: { 'Http-Authorization' => Breeze.api_token })
69
+ .to_return(status: 200, body: resource_response)
70
+ @breeze = Breeze.find 1
71
+ end
72
+ it 'performs the API root request' do
73
+ expect(root_request).to have_been_requested
74
+ end
75
+ it 'performs the resource request' do
76
+ expect(@resource_request).to have_been_requested
77
+ end
78
+ it 'returns an item with the correct class' do
79
+ expect(@breeze).to be_a_kind_of Breeze
80
+ end
81
+ it 'returns an item with the correct attributes' do
82
+ expect(@breeze.id).to eq resource_response['id']
83
+ expect(@breeze.title).to eq resource_response['title']
84
+ end
85
+ end
86
+
87
+ context '.create' do
88
+ before(:each) do
89
+ root_request
90
+ @create_request = WebMock.stub_request(:post, "#{Breeze.api_root}/breezes")
91
+ .with(headers: { 'Http-Authorization' => Breeze.api_token })
92
+ .to_return(status: 201, body: resource_response)
93
+ @params = { title: 'What a nice breeze.' }
94
+ @breeze = Breeze.create(@params)
95
+ end
96
+ it 'performs the API root request' do
97
+ expect(root_request).to have_been_requested
98
+ end
99
+ it 'performs the create request' do
100
+ expect(@create_request).to have_been_requested
101
+ end
102
+ it 'returns an item with the correct class' do
103
+ expect(@breeze).to be_a_kind_of Breeze
104
+ end
105
+ it 'returns an item with the correct attributes' do
106
+ expect(@breeze.id).to eq resource_response['id']
107
+ expect(@breeze.title).to eq resource_response['title']
108
+ end
109
+ end
110
+ end
111
+ context 'instance methods' do
112
+ it 'sets to_param string based on instance.id' do
113
+ expect(Breeze.new(id: 1).to_param).to eq 1.to_s
114
+ end
115
+
116
+ context '#save' do
117
+ before(:each) do
118
+ root_request
119
+ @save_request = WebMock.stub_request(:patch, "#{Breeze.api_root}/breezes/1")
120
+ .with(headers: { 'Http-Authorization' => Breeze.api_token })
121
+ .to_return(status: 200, body: resource_response)
122
+ @params = { title: 'What a nice breeze.' }
123
+ @breeze = Breeze.new(id: 1).save(@params)
124
+ end
125
+ it 'performs the API root request' do
126
+ expect(root_request).to have_been_requested
127
+ end
128
+ it 'performs the save request' do
129
+ expect(@save_request).to have_been_requested
130
+ end
131
+ it 'returns an item with the correct class' do
132
+ expect(@breeze).to be_a_kind_of Breeze
133
+ end
134
+ it 'returns an item with the correct attributes' do
135
+ expect(@breeze.id).to eq resource_response['id']
136
+ expect(@breeze.title).to eq resource_response['title']
137
+ end
138
+ end
139
+
140
+ context '#destroy' do
141
+ before(:each) do
142
+ root_request
143
+ @destroy_request = WebMock.stub_request(:delete, "#{Breeze.api_root}/breezes/1")
144
+ .with(headers: { 'Http-Authorization' => Breeze.api_token })
145
+ .to_return(status: 200)
146
+ @resource_request = WebMock.stub_request(:get, "#{Breeze.api_root}/breezes/1")
147
+ .with(headers: { 'Http-Authorization' => Breeze.api_token })
148
+ .to_return(status: 200, body: resource_response)
149
+ @breeze = Breeze.new(id: 1).destroy
150
+ end
151
+ it 'performs the API root request' do
152
+ expect(root_request).to have_been_requested
153
+ end
154
+ it 'performs the destroy request' do
155
+ expect(@destroy_request).to have_been_requested
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,23 @@
1
+ {
2
+ "total_count":1,
3
+ "total_pages":1,
4
+ "current_page":1,
5
+ "_links":{
6
+ "self":{
7
+ "href":"http://localhost:9292/breezes"
8
+ }
9
+ },
10
+ "_embedded":{
11
+ "breezes":[
12
+ {
13
+ "id":1,
14
+ "title":"Southerly Breeze",
15
+ "_links":{
16
+ "self":{
17
+ "href":"http://localhost:9292/breezes/1"
18
+ }
19
+ }
20
+ }
21
+ ]
22
+ }
23
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "id":1,
3
+ "title":"Southerly Breeze",
4
+ "created_at":"2015-02-11T18:59:01.669Z",
5
+ "updated_at":"2015-02-12T00:26:22.255Z",
6
+ "_links":{
7
+ "self":{
8
+ "href":"http://example.org/breezes/1"
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "_links":{
3
+ "self":{
4
+ "href":"http://localhost:9292"
5
+ },
6
+ "health":{
7
+ "href":"http://localhost:9292/health"
8
+ },
9
+ "breezes":{
10
+ "href":"http://localhost:9292/breezes{?page,size}",
11
+ "templated":true
12
+ },
13
+ "breeze":{
14
+ "href":"http://localhost:9292/breezes/{id}",
15
+ "templated":true
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ Dir["#{File.dirname(__FILE__)}/shared/**/*.rb"].each do |file|
5
+ require file
6
+ end
7
+
8
+ Dir["#{File.dirname(__FILE__)}/fixtures/**/*.rb"].each do |file|
9
+ require file
10
+ end
11
+
12
+ # Require library up front
13
+ require 'braque'
14
+ require 'shoulda/matchers'
15
+ require 'webmock/rspec'
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: braque
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Dylan Fareed
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hyperclient
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: active_attr
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.8.5
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.5
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 4.2.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 4.2.0
55
+ description: Braque provides a simple interface for interacting with Hypermedia API
56
+ services in Ruby apps.
57
+ email:
58
+ - email@dylanfareed.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - MIT-LICENSE
64
+ - README.md
65
+ - lib/braque.rb
66
+ - lib/braque/collection.rb
67
+ - lib/braque/model.rb
68
+ - lib/braque/version.rb
69
+ - spec/braque/collection_spec.rb
70
+ - spec/braque/model_spec.rb
71
+ - spec/fixtures/collection.json
72
+ - spec/fixtures/resource.json
73
+ - spec/fixtures/root.json
74
+ - spec/spec_helper.rb
75
+ homepage: https://github.com/dylanfareed/braque
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.4.3
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Braque provides a simple interface for interacting with Hypermedia API services
99
+ in Ruby apps.
100
+ test_files: []