braque 0.0.1

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: 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: []