hypa 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -1
- data/Gemfile.lock +1 -31
- data/README.md +16 -1
- data/examples/sinatra/Gemfile +9 -0
- data/examples/sinatra/Gemfile.lock +36 -0
- data/examples/sinatra/application.rb +55 -0
- data/hypa.gemspec +0 -9
- data/lib/hypa.rb +62 -117
- data/lib/hypa/version.rb +1 -1
- data/spec/integration/action_spec.rb +22 -0
- data/spec/integration/collection_spec.rb +46 -0
- data/spec/spec_helper.rb +0 -2
- data/spec/unit/hypa_spec.rb +57 -0
- metadata +11 -131
- data/examples/todo.rb +0 -52
- data/spec/lib/hypa_spec.rb +0 -294
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ab61fe787dba85c43941f98b2edeae319a7dccb
|
4
|
+
data.tar.gz: 027ea139b2f90ff9c27d6285aa8bd3a1d7e07e05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd2f20ff25e4f4277df85623564ed534717bfcbc360d38e0f468bbb0870a74987e8d860ae16e41ce41278fdb42a0c0248e93665d58e78c3c3e970113d08b7111
|
7
|
+
data.tar.gz: a49106096218c76ba4c77b758bdbb5ccc7ae684dd41e9863ba0a464b415e7d453d5b3c5ff231a04fe74c8994a85525c97f65eb53ade6ac48d6959bf958f81feb
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,19 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
hypa (0.0.
|
5
|
-
addressable
|
4
|
+
hypa (0.0.2)
|
6
5
|
extlib
|
7
|
-
multi_json
|
8
|
-
sequel
|
9
|
-
sinatra
|
10
|
-
virtus
|
11
6
|
|
12
7
|
GEM
|
13
8
|
remote: https://rubygems.org/
|
14
9
|
specs:
|
15
|
-
addressable (2.3.4)
|
16
|
-
backports (2.6.7)
|
17
10
|
colorize (0.5.8)
|
18
11
|
coveralls (0.6.7)
|
19
12
|
colorize
|
@@ -21,19 +14,10 @@ GEM
|
|
21
14
|
rest-client
|
22
15
|
simplecov (>= 0.7)
|
23
16
|
thor
|
24
|
-
descendants_tracker (0.0.1)
|
25
17
|
diff-lcs (1.2.4)
|
26
18
|
extlib (0.9.16)
|
27
19
|
mime-types (1.23)
|
28
20
|
multi_json (1.7.2)
|
29
|
-
oj (2.0.12)
|
30
|
-
puma (2.0.1)
|
31
|
-
rack (>= 1.1, < 2.0)
|
32
|
-
rack (1.5.2)
|
33
|
-
rack-protection (1.5.0)
|
34
|
-
rack
|
35
|
-
rack-test (0.6.2)
|
36
|
-
rack (>= 1.0)
|
37
21
|
rake (0.9.6)
|
38
22
|
rest-client (1.6.7)
|
39
23
|
mime-types (>= 1.16)
|
@@ -45,21 +29,11 @@ GEM
|
|
45
29
|
rspec-expectations (2.13.0)
|
46
30
|
diff-lcs (>= 1.1.3, < 2.0)
|
47
31
|
rspec-mocks (2.13.1)
|
48
|
-
sequel (3.47.0)
|
49
32
|
simplecov (0.7.1)
|
50
33
|
multi_json (~> 1.0)
|
51
34
|
simplecov-html (~> 0.7.1)
|
52
35
|
simplecov-html (0.7.1)
|
53
|
-
sinatra (1.4.2)
|
54
|
-
rack (~> 1.5, >= 1.5.2)
|
55
|
-
rack-protection (~> 1.4)
|
56
|
-
tilt (~> 1.3, >= 1.3.4)
|
57
|
-
sqlite3 (1.3.7)
|
58
36
|
thor (0.18.1)
|
59
|
-
tilt (1.4.0)
|
60
|
-
virtus (0.5.4)
|
61
|
-
backports (~> 2.6.1)
|
62
|
-
descendants_tracker (~> 0.0.1)
|
63
37
|
|
64
38
|
PLATFORMS
|
65
39
|
ruby
|
@@ -67,10 +41,6 @@ PLATFORMS
|
|
67
41
|
DEPENDENCIES
|
68
42
|
coveralls
|
69
43
|
hypa!
|
70
|
-
oj
|
71
|
-
puma
|
72
|
-
rack-test
|
73
44
|
rake
|
74
45
|
rspec
|
75
46
|
simplecov
|
76
|
-
sqlite3
|
data/README.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
1
|
# Hypa: Web Framework to make Hypermedia APIs
|
2
2
|
|
3
|
-
[![Gem Version](https://badge.fury.io/rb/hypa.png)] [![Build Status](https://travis-ci.org/nmerouze/hypa.png?branch=master)](https://travis-ci.org/nmerouze/hypa) [![Code Climate](https://codeclimate.com/github/nmerouze/hypa.png)](https://codeclimate.com/github/nmerouze/hypa) [![Dependency Status](https://gemnasium.com/nmerouze/hypa.png)](https://gemnasium.com/nmerouze/hypa) [![Coverage Status](https://coveralls.io/repos/nmerouze/hypa/badge.png?branch=master)](https://coveralls.io/r/nmerouze/hypa)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/hypa.png)](http://rubygems.org/gems/hypa) [![Build Status](https://travis-ci.org/nmerouze/hypa.png?branch=master)](https://travis-ci.org/nmerouze/hypa) [![Code Climate](https://codeclimate.com/github/nmerouze/hypa.png)](https://codeclimate.com/github/nmerouze/hypa) [![Dependency Status](https://gemnasium.com/nmerouze/hypa.png)](https://gemnasium.com/nmerouze/hypa) [![Coverage Status](https://coveralls.io/repos/nmerouze/hypa/badge.png?branch=master)](https://coveralls.io/r/nmerouze/hypa)
|
4
|
+
|
5
|
+
# List of Hypermedia content types
|
6
|
+
|
7
|
+
* Collection+JSON
|
8
|
+
* HAL
|
9
|
+
* Siren
|
10
|
+
* [JSON API](http://jsonapi.org)
|
11
|
+
|
12
|
+
# TODO
|
13
|
+
|
14
|
+
* Root element should be the name of the collection
|
15
|
+
* Refactoring and improve Collection#render
|
16
|
+
* Nested attribute sets
|
17
|
+
* Filter a hash through action params
|
18
|
+
* More features (descriptions, validations, etc)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../../
|
3
|
+
specs:
|
4
|
+
hypa (0.0.2)
|
5
|
+
extlib
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
extlib (0.9.16)
|
11
|
+
multi_json (1.7.2)
|
12
|
+
oj (2.0.12)
|
13
|
+
puma (2.0.1)
|
14
|
+
rack (>= 1.1, < 2.0)
|
15
|
+
rack (1.5.2)
|
16
|
+
rack-protection (1.5.0)
|
17
|
+
rack
|
18
|
+
sequel (3.47.0)
|
19
|
+
sinatra (1.4.2)
|
20
|
+
rack (~> 1.5, >= 1.5.2)
|
21
|
+
rack-protection (~> 1.4)
|
22
|
+
tilt (~> 1.3, >= 1.3.4)
|
23
|
+
sqlite3 (1.3.7)
|
24
|
+
tilt (1.4.0)
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
hypa!
|
31
|
+
multi_json
|
32
|
+
oj
|
33
|
+
puma
|
34
|
+
sequel
|
35
|
+
sinatra
|
36
|
+
sqlite3
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'sequel'
|
4
|
+
require 'multi_json'
|
5
|
+
require 'hypa'
|
6
|
+
|
7
|
+
DB = Sequel.sqlite
|
8
|
+
|
9
|
+
DB.create_table(:posts) do
|
10
|
+
primary_key :id
|
11
|
+
String :title
|
12
|
+
String :body
|
13
|
+
end
|
14
|
+
|
15
|
+
class Post < Sequel::Model
|
16
|
+
end
|
17
|
+
|
18
|
+
Post.insert(title: 'First Title', body: 'This is the first post')
|
19
|
+
|
20
|
+
class Blog < Hypa::Application
|
21
|
+
end
|
22
|
+
|
23
|
+
Blog.collection :posts do |c|
|
24
|
+
c.schema do |s|
|
25
|
+
s.attribute :id, type: 'integer'
|
26
|
+
s.attribute :title, type: 'string'
|
27
|
+
s.attribute :body, type: 'string'
|
28
|
+
end
|
29
|
+
|
30
|
+
c.action do |a|
|
31
|
+
a.rel :self
|
32
|
+
a.href '/posts'
|
33
|
+
|
34
|
+
a.params do |p|
|
35
|
+
p.attribute :limit, type: 'integer'
|
36
|
+
# p.set :title do |s|
|
37
|
+
# s.attribute :in, type: 'array'
|
38
|
+
# s.attribute :like, type: 'string'
|
39
|
+
# end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
before do
|
45
|
+
content_type 'application/hypa+json'
|
46
|
+
end
|
47
|
+
|
48
|
+
include Sinatra
|
49
|
+
|
50
|
+
get '/posts' do
|
51
|
+
template = Blog.collections[:posts]
|
52
|
+
# parameters = template.actions[:self].filter(params)
|
53
|
+
posts = Post.all#(parameters)
|
54
|
+
MultiJson.dump(template.render(posts))
|
55
|
+
end
|
data/hypa.gemspec
CHANGED
@@ -17,19 +17,10 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.require_path = 'lib'
|
18
18
|
s.required_ruby_version = '>= 1.9.3'
|
19
19
|
|
20
|
-
s.add_dependency 'virtus'
|
21
20
|
s.add_dependency 'extlib'
|
22
|
-
s.add_dependency 'multi_json'
|
23
|
-
s.add_dependency 'sinatra'
|
24
|
-
s.add_dependency 'addressable'
|
25
|
-
s.add_dependency 'sequel'
|
26
21
|
|
27
22
|
s.add_development_dependency 'rspec'
|
28
|
-
s.add_development_dependency 'rack-test'
|
29
23
|
s.add_development_dependency 'rake'
|
30
|
-
s.add_development_dependency 'sqlite3'
|
31
|
-
s.add_development_dependency 'oj'
|
32
|
-
s.add_development_dependency 'puma'
|
33
24
|
s.add_development_dependency 'simplecov'
|
34
25
|
s.add_development_dependency 'coveralls'
|
35
26
|
end
|
data/lib/hypa.rb
CHANGED
@@ -1,158 +1,103 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
2
|
require 'bundler/setup'
|
4
|
-
require 'virtus'
|
5
|
-
require 'sinatra'
|
6
|
-
require 'multi_json'
|
7
|
-
require 'addressable/template'
|
8
|
-
require 'rack/utils'
|
9
3
|
require 'extlib/class'
|
10
|
-
require 'extlib/inflection'
|
11
4
|
|
12
5
|
module Hypa
|
13
6
|
class Attribute
|
14
|
-
|
15
|
-
|
16
|
-
attribute :name, String
|
17
|
-
attribute :value, String
|
18
|
-
attribute :prompt, String
|
19
|
-
end
|
7
|
+
attr_reader :name, :type
|
20
8
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
attribute :rel, String
|
25
|
-
attribute :href, String
|
26
|
-
end
|
27
|
-
|
28
|
-
class Item
|
29
|
-
include Virtus
|
30
|
-
|
31
|
-
attribute :href, String
|
32
|
-
attribute :data, Array[Attribute]
|
33
|
-
attribute :links, Array[Link]
|
34
|
-
|
35
|
-
def render(data)
|
36
|
-
self.href = Addressable::Template.new(self.href).expand(data).to_s
|
37
|
-
self.data.each { |a| a.value = Rack::Utils.unescape(Addressable::Template.new(a.value).expand(data).to_s) }
|
38
|
-
self
|
9
|
+
def initialize(properties = {})
|
10
|
+
@name = properties.delete(:name)
|
11
|
+
@type = properties.delete(:type)
|
39
12
|
end
|
40
|
-
end
|
41
|
-
|
42
|
-
class Template
|
43
|
-
include Virtus
|
44
13
|
|
45
|
-
|
14
|
+
def to_hash
|
15
|
+
{ name: @name.to_s, type: @type.to_s }
|
16
|
+
end
|
46
17
|
end
|
47
18
|
|
48
|
-
class
|
49
|
-
|
19
|
+
class AttributeSet
|
20
|
+
attr_reader :attributes
|
50
21
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
22
|
+
def initialize(&block)
|
23
|
+
@attributes = []
|
24
|
+
block.call(self) if block_given?
|
25
|
+
end
|
56
26
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
attribute :version, String
|
61
|
-
attribute :href, String
|
62
|
-
attribute :links, Array[Link]
|
63
|
-
attribute :items, Array[Item]
|
64
|
-
attribute :template, Template
|
65
|
-
attribute :queries, Array[Query]
|
66
|
-
end
|
27
|
+
def attribute(name, properties = {})
|
28
|
+
@attributes << Attribute.new(properties.merge(name: name))
|
29
|
+
end
|
67
30
|
|
68
|
-
|
69
|
-
|
31
|
+
# def set(name, &block)
|
32
|
+
# @sets[name] << AttributeSet.new(&block)
|
33
|
+
# end
|
70
34
|
|
71
|
-
def
|
72
|
-
|
35
|
+
def to_hash
|
36
|
+
@attributes.map { |a| a.to_hash }
|
73
37
|
end
|
74
38
|
end
|
75
39
|
|
76
|
-
class
|
77
|
-
|
78
|
-
|
79
|
-
set :show_exceptions, false
|
80
|
-
|
81
|
-
before do
|
82
|
-
content_type 'application/vnd.collection+json'
|
40
|
+
class Action
|
41
|
+
def initialize(&block)
|
42
|
+
block.call(self) if block_given?
|
83
43
|
end
|
84
44
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
if collection = @@collections[coll_name.to_sym]
|
89
|
-
items = Database.all(coll_name)
|
90
|
-
|
91
|
-
collection.items = items.map do |data|
|
92
|
-
@@items[coll_name.singular.to_sym].clone.render(data)
|
93
|
-
end
|
94
|
-
|
95
|
-
MultiJson.dump(collection.to_hash, mode: :compat)
|
96
|
-
else
|
97
|
-
status 404
|
98
|
-
MultiJson.dump({ error: 'Not found.' }, mode: :compat)
|
99
|
-
end
|
45
|
+
def rel(value)
|
46
|
+
@rel = value
|
100
47
|
end
|
101
48
|
|
102
|
-
|
49
|
+
def href(value)
|
50
|
+
@href = value
|
103
51
|
end
|
104
52
|
|
105
|
-
|
53
|
+
def params(&block)
|
54
|
+
@params = AttributeSet.new(&block)
|
106
55
|
end
|
107
56
|
|
108
|
-
|
57
|
+
def to_hash
|
58
|
+
{ rel: @rel.to_s, href: @href.to_s, params: @params.to_hash }
|
109
59
|
end
|
60
|
+
end
|
110
61
|
|
111
|
-
|
62
|
+
class Collection
|
63
|
+
def initialize(&block)
|
64
|
+
@actions = []
|
65
|
+
block.call(self) if block_given?
|
112
66
|
end
|
113
67
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
if block_given?
|
119
|
-
template = Template.new
|
120
|
-
block.call(template)
|
121
|
-
@@templates[name] = template
|
122
|
-
else
|
123
|
-
template = @@templates[name]
|
124
|
-
end
|
68
|
+
def schema(&block)
|
69
|
+
@schema = AttributeSet.new(&block)
|
70
|
+
end
|
125
71
|
|
126
|
-
|
127
|
-
|
72
|
+
def action(&block)
|
73
|
+
@actions << Action.new(&block)
|
74
|
+
end
|
128
75
|
|
129
|
-
|
130
|
-
|
76
|
+
def to_hash
|
77
|
+
{ schema: @schema.to_hash, actions: @actions.map { |a| a.to_hash } }
|
78
|
+
end
|
131
79
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
@@collections[name] = collection
|
136
|
-
else
|
137
|
-
collection = @@collections[name]
|
138
|
-
end
|
80
|
+
# TODO: Refactoring
|
81
|
+
def render(data)
|
82
|
+
attributes = @schema.attributes.map { |a| a.name }
|
139
83
|
|
140
|
-
|
84
|
+
items = data.map do |d|
|
85
|
+
item = {}
|
86
|
+
attributes.each { |a| item[a] = d[a] }
|
87
|
+
item
|
141
88
|
end
|
89
|
+
|
90
|
+
self.to_hash.merge(items: items)
|
91
|
+
end
|
92
|
+
end
|
142
93
|
|
143
|
-
|
144
|
-
|
94
|
+
class Application
|
95
|
+
cattr_reader :collections
|
145
96
|
|
146
|
-
|
147
|
-
item = Item.new
|
148
|
-
block.call(item)
|
149
|
-
@@items[name] = item
|
150
|
-
else
|
151
|
-
item = @@items[name]
|
152
|
-
end
|
97
|
+
@@collections = {}
|
153
98
|
|
154
|
-
|
155
|
-
|
99
|
+
def self.collection(name, &block)
|
100
|
+
@@collections[name] = Collection.new(&block)
|
156
101
|
end
|
157
102
|
end
|
158
103
|
end
|
data/lib/hypa/version.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Hypa::Action.new' do
|
4
|
+
before do
|
5
|
+
@action = Hypa::Action.new do |a|
|
6
|
+
a.rel :self
|
7
|
+
a.href '/posts'
|
8
|
+
|
9
|
+
a.params do |p|
|
10
|
+
p.attribute :title, type: 'string'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
@properties = @action.to_hash
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'defines rel, href and params' do
|
18
|
+
expect(@properties[:rel]).to eq('self')
|
19
|
+
expect(@properties[:href]).to eq('/posts')
|
20
|
+
expect(@properties[:params]).to eq([{ name: 'title', type: 'string' }])
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hypa::Collection, '.new' do
|
4
|
+
before do
|
5
|
+
@resource = Hypa::Collection.new do |c|
|
6
|
+
c.schema do |s|
|
7
|
+
s.attribute(:title, type: 'string')
|
8
|
+
end
|
9
|
+
|
10
|
+
c.action do |a|
|
11
|
+
a.rel :search
|
12
|
+
a.href '/posts/search'
|
13
|
+
|
14
|
+
a.params do |p|
|
15
|
+
p.attribute :q, type: 'string'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
@properties = @resource.to_hash
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'defines schema' do
|
24
|
+
expect(@properties[:schema]).to eq([{ name: 'title', type: 'string' }])
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'defines actions' do
|
28
|
+
expect(@properties[:actions]).to eq([{ rel: 'search', href: '/posts/search', params: [{ name: 'q', type: 'string' }] }])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe Hypa::Collection, '#render' do
|
33
|
+
before do
|
34
|
+
@resource = Hypa::Collection.new do |c|
|
35
|
+
c.schema do |s|
|
36
|
+
s.attribute(:title, type: 'string')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
@properties = @resource.render([{ title: 'My Test Title' }])
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'renders items of the resource' do
|
44
|
+
expect(@properties[:items]).to eq([{ title: 'My Test Title' }])
|
45
|
+
end
|
46
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hypa::Attribute, '#initialize' do
|
4
|
+
before do
|
5
|
+
@attribute = Hypa::Attribute.new(name: 'title', type: 'string')
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'stores name' do
|
9
|
+
expect(@attribute.name).to eq('title')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'stores type' do
|
13
|
+
expect(@attribute.type).to eq('string')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Hypa::AttributeSet, '#attribute' do
|
18
|
+
before do
|
19
|
+
@attribute = double('Hypa::Attribute')
|
20
|
+
Hypa::Attribute.stub(:new).and_return(@attribute)
|
21
|
+
@set = Hypa::AttributeSet.new
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'initializes attribute with name and type' do
|
25
|
+
Hypa::Attribute.should_receive(:new).with(name: 'title', type: 'string')
|
26
|
+
@set.attribute('title', type: 'string')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'stores attribute' do
|
30
|
+
@set.attribute('title', type: 'string')
|
31
|
+
expect(@set.attributes).to eq([@attribute])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# describe Hypa::Action, '#to_hash' do
|
36
|
+
# it 'serializes properties to a hash'
|
37
|
+
# it 'casts rel to string'
|
38
|
+
# it 'casts href to string'
|
39
|
+
# it 'serializes params'
|
40
|
+
# end
|
41
|
+
|
42
|
+
# describe Hypa::AttributeSet, '#to_hash' do
|
43
|
+
# it 'serializes attributes into an array of hashes'
|
44
|
+
# end
|
45
|
+
|
46
|
+
# describe Hypa::Attribute, '#to_hash' do
|
47
|
+
# it 'serializes properties to a hash'
|
48
|
+
# it 'casts name to string'
|
49
|
+
# it 'casts type to string'
|
50
|
+
# end
|
51
|
+
|
52
|
+
describe Hypa::Application, '.collection' do
|
53
|
+
it 'stores a resource' do
|
54
|
+
Hypa::Application.collection :posts, &Proc.new {}
|
55
|
+
expect(Hypa::Application.collections[:posts]).to be_a(Hypa::Collection)
|
56
|
+
end
|
57
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hypa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicolas Mérouze
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-05-
|
11
|
+
date: 2013-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: virtus
|
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
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: extlib
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,62 +24,6 @@ dependencies:
|
|
38
24
|
- - '>='
|
39
25
|
- !ruby/object:Gem::Version
|
40
26
|
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: multi_json
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - '>='
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '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'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: sinatra
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - '>='
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - '>='
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: addressable
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - '>='
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - '>='
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: sequel
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - '>='
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :runtime
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - '>='
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
27
|
- !ruby/object:Gem::Dependency
|
98
28
|
name: rspec
|
99
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,20 +38,6 @@ dependencies:
|
|
108
38
|
- - '>='
|
109
39
|
- !ruby/object:Gem::Version
|
110
40
|
version: '0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: rack-test
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - '>='
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - '>='
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
125
41
|
- !ruby/object:Gem::Dependency
|
126
42
|
name: rake
|
127
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,48 +52,6 @@ dependencies:
|
|
136
52
|
- - '>='
|
137
53
|
- !ruby/object:Gem::Version
|
138
54
|
version: '0'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: sqlite3
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - '>='
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - '>='
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: oj
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - '>='
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '0'
|
160
|
-
type: :development
|
161
|
-
prerelease: false
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - '>='
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: puma
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - '>='
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - '>='
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
181
55
|
- !ruby/object:Gem::Dependency
|
182
56
|
name: simplecov
|
183
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -221,12 +95,16 @@ files:
|
|
221
95
|
- Gemfile.lock
|
222
96
|
- README.md
|
223
97
|
- Rakefile
|
224
|
-
- examples/
|
98
|
+
- examples/sinatra/Gemfile
|
99
|
+
- examples/sinatra/Gemfile.lock
|
100
|
+
- examples/sinatra/application.rb
|
225
101
|
- hypa.gemspec
|
226
102
|
- lib/hypa.rb
|
227
103
|
- lib/hypa/version.rb
|
228
|
-
- spec/
|
104
|
+
- spec/integration/action_spec.rb
|
105
|
+
- spec/integration/collection_spec.rb
|
229
106
|
- spec/spec_helper.rb
|
107
|
+
- spec/unit/hypa_spec.rb
|
230
108
|
homepage: https://github.com/nmerouze/hypa
|
231
109
|
licenses:
|
232
110
|
- MIT
|
@@ -252,5 +130,7 @@ signing_key:
|
|
252
130
|
specification_version: 4
|
253
131
|
summary: Web Framework to make Hypermedia APIs
|
254
132
|
test_files:
|
255
|
-
- spec/
|
133
|
+
- spec/integration/action_spec.rb
|
134
|
+
- spec/integration/collection_spec.rb
|
256
135
|
- spec/spec_helper.rb
|
136
|
+
- spec/unit/hypa_spec.rb
|
data/examples/todo.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
require File.expand_path('../../lib/hypa', __FILE__)
|
2
|
-
require 'sequel'
|
3
|
-
|
4
|
-
Hypa::Database.connection = Sequel.sqlite
|
5
|
-
|
6
|
-
Hypa::Database.connection.create_table(:posts) do
|
7
|
-
primary_key :id
|
8
|
-
String :title
|
9
|
-
end
|
10
|
-
|
11
|
-
Hypa::Database.connection.from(:posts).insert(title: 'What a title!')
|
12
|
-
|
13
|
-
class Todo < Hypa::Application
|
14
|
-
end
|
15
|
-
|
16
|
-
Todo.item :post do |i|
|
17
|
-
i.href = '/posts/{id}'
|
18
|
-
|
19
|
-
i.data = [
|
20
|
-
{ name: 'title', value: '{title}', prompt: 'Title' }
|
21
|
-
]
|
22
|
-
end
|
23
|
-
|
24
|
-
Todo.template :post do |t|
|
25
|
-
t.data = [
|
26
|
-
{ name: 'title', value: '', prompt: 'Title' }
|
27
|
-
]
|
28
|
-
end
|
29
|
-
|
30
|
-
Todo.template :search do |t|
|
31
|
-
t.data = [
|
32
|
-
{ name: 'q', value: '', prompt: 'Search' }
|
33
|
-
]
|
34
|
-
end
|
35
|
-
|
36
|
-
Todo.collection :posts do |c|
|
37
|
-
c.version = '1.0'
|
38
|
-
c.href = '/posts'
|
39
|
-
|
40
|
-
c.links = [
|
41
|
-
{ rel: 'feed', href: '/posts/feed' }
|
42
|
-
]
|
43
|
-
|
44
|
-
c.queries = [
|
45
|
-
{ rel: 'search', href: '/posts/search', prompt: 'Search', data: Todo.template(:search).data }
|
46
|
-
]
|
47
|
-
|
48
|
-
c.template = Todo.template(:post)
|
49
|
-
end
|
50
|
-
|
51
|
-
# https://github.com/rkh/mustermann
|
52
|
-
# https://github.com/awslabs/seahorse
|
data/spec/lib/hypa_spec.rb
DELETED
@@ -1,294 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe Hypa::Attribute do
|
6
|
-
it 'stores name' do
|
7
|
-
a = Hypa::Attribute.new(:name => 'full_name')
|
8
|
-
expect(a.name).to eq('full_name')
|
9
|
-
end
|
10
|
-
|
11
|
-
it 'stores value' do
|
12
|
-
a = Hypa::Attribute.new(:value => 'Nicolas Mérouze')
|
13
|
-
expect(a.value).to eq('Nicolas Mérouze')
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'stores prompt' do
|
17
|
-
a = Hypa::Attribute.new(:prompt => 'Full Name')
|
18
|
-
expect(a.prompt).to eq('Full Name')
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
describe Hypa::Link do
|
23
|
-
it 'stores rel' do
|
24
|
-
l = Hypa::Link.new(:rel => 'feed')
|
25
|
-
expect(l.rel).to eq('feed')
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'stores href' do
|
29
|
-
l = Hypa::Link.new(:href => 'http://example.org/posts/feed')
|
30
|
-
expect(l.href).to eq('http://example.org/posts/feed')
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
describe Hypa::Item do
|
35
|
-
it 'stores href' do
|
36
|
-
i = Hypa::Item.new(:href => 'http://example.org/posts/1')
|
37
|
-
expect(i.href).to eq('http://example.org/posts/1')
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'stores data' do
|
41
|
-
a = Hypa::Attribute.new
|
42
|
-
item = Hypa::Item.new(:data => [a])
|
43
|
-
expect(item.data).to eq([a])
|
44
|
-
end
|
45
|
-
|
46
|
-
it 'stores links' do
|
47
|
-
i = Hypa::Item.new(:links => [])
|
48
|
-
expect(i.links).to eq([])
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe Hypa::Item, '#render' do
|
53
|
-
before do
|
54
|
-
@item = Hypa::Item.new(href: '/posts/{id}', data: [
|
55
|
-
{ name: 'title', value: '{title}', prompt: 'Title' }
|
56
|
-
])
|
57
|
-
@item.render(id: 1, title: 'My Custom Title')
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'renders href with passed data' do
|
61
|
-
expect(@item.href).to eq('/posts/1')
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'renders data values with passed data' do
|
65
|
-
expect(@item.data.first[:value]).to eq('My Custom Title')
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
describe Hypa::Template do
|
70
|
-
it 'stores data' do
|
71
|
-
a = Hypa::Attribute.new
|
72
|
-
t = Hypa::Template.new(:data => [a])
|
73
|
-
expect(t.data).to eq([a])
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
describe Hypa::Query do
|
78
|
-
it 'stores data' do
|
79
|
-
a = Hypa::Attribute.new
|
80
|
-
q = Hypa::Query.new(:data => [a])
|
81
|
-
expect(q.data).to eq([a])
|
82
|
-
end
|
83
|
-
|
84
|
-
it 'stores rel' do
|
85
|
-
q = Hypa::Query.new(:rel => 'feed')
|
86
|
-
expect(q.rel).to eq('feed')
|
87
|
-
end
|
88
|
-
|
89
|
-
it 'stores href' do
|
90
|
-
q = Hypa::Link.new(:href => 'http://example.org/posts/feed')
|
91
|
-
expect(q.href).to eq('http://example.org/posts/feed')
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'stores prompt' do
|
95
|
-
q = Hypa::Query.new(:prompt => 'Full Name')
|
96
|
-
expect(q.prompt).to eq('Full Name')
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
describe Hypa::Collection do
|
101
|
-
it 'stores version' do
|
102
|
-
c = Hypa::Collection.new(:version => '1.0')
|
103
|
-
expect(c.version).to eq('1.0')
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'stores href' do
|
107
|
-
c = Hypa::Collection.new(:href => 'http://example.org/posts/')
|
108
|
-
expect(c.href).to eq('http://example.org/posts/')
|
109
|
-
end
|
110
|
-
|
111
|
-
it 'stores links' do
|
112
|
-
l = Hypa::Link.new
|
113
|
-
c = Hypa::Collection.new(:links => [l])
|
114
|
-
expect(c.links).to eq([l])
|
115
|
-
end
|
116
|
-
|
117
|
-
it 'stores items' do
|
118
|
-
i = Hypa::Item.new
|
119
|
-
c = Hypa::Collection.new(:items => [i])
|
120
|
-
expect(c.items).to eq([i])
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'stores queries' do
|
124
|
-
q = Hypa::Query.new
|
125
|
-
c = Hypa::Collection.new(:queries => [q])
|
126
|
-
expect(c.queries).to eq([q])
|
127
|
-
end
|
128
|
-
|
129
|
-
it 'stores template' do
|
130
|
-
t = Hypa::Template.new
|
131
|
-
c = Hypa::Collection.new(:template => t)
|
132
|
-
expect(c.template).to eq(t)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
describe Hypa::Application, '.template' do
|
137
|
-
before do
|
138
|
-
@template = Hypa::Template.new
|
139
|
-
Hypa::Template.stub(:new => @template)
|
140
|
-
|
141
|
-
Hypa::Application.template :mock do |t|
|
142
|
-
t.data = [
|
143
|
-
{ name: 'full_name', value: '', prompt: 'Full Name' }
|
144
|
-
]
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
context 'with block' do
|
149
|
-
it 'initializes a template with block' do
|
150
|
-
expect(@template.data.size).to eq(1)
|
151
|
-
expect(@template.data.first.attributes).to eq(name: 'full_name', value: '', prompt: 'Full Name')
|
152
|
-
end
|
153
|
-
|
154
|
-
it 'stores a template' do
|
155
|
-
expect(Hypa::Application.templates).to eq({ mock: @template })
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
context 'without block' do
|
160
|
-
it 'returns a template' do
|
161
|
-
expect(Hypa::Application.template(:mock)).to eq(@template)
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
describe Hypa::Application, '.collection' do
|
167
|
-
before do
|
168
|
-
@collection = Hypa::Collection.new
|
169
|
-
Hypa::Collection.stub(:new => @collection)
|
170
|
-
|
171
|
-
Hypa::Application.collection :mock do |c|
|
172
|
-
c.version = 'custom version'
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
context 'with block' do
|
177
|
-
it 'initializes a collection with block' do
|
178
|
-
expect(@collection.version).to eq('custom version')
|
179
|
-
end
|
180
|
-
|
181
|
-
it 'stores a collection' do
|
182
|
-
expect(Hypa::Application.collections).to eq(mock: @collection)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
context 'without block' do
|
187
|
-
it 'returns a collection' do
|
188
|
-
expect(Hypa::Application.collection(:mock)).to eq(@collection)
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
describe Hypa::Application, '.item' do
|
194
|
-
before do
|
195
|
-
@item = Hypa::Item.new
|
196
|
-
Hypa::Item.stub(:new => @item)
|
197
|
-
|
198
|
-
Hypa::Application.item :mock do |i|
|
199
|
-
i.href = '/posts/{id}'
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
context 'with block' do
|
204
|
-
it 'initializes am item with block' do
|
205
|
-
expect(@item.href).to eq('/posts/{id}')
|
206
|
-
end
|
207
|
-
|
208
|
-
it 'stores a item' do
|
209
|
-
expect(Hypa::Application.items).to eq(mock: @item)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
context 'without block' do
|
214
|
-
it 'returns a item' do
|
215
|
-
expect(Hypa::Application.item(:mock)).to eq(@item)
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
describe 'GET /:collection' do
|
221
|
-
include Rack::Test::Methods
|
222
|
-
|
223
|
-
def app
|
224
|
-
Hypa::Application
|
225
|
-
end
|
226
|
-
|
227
|
-
context 'with existing collection' do
|
228
|
-
before do
|
229
|
-
Hypa::Database.stub(:all).and_return([{id: 1234, title: 'My Test'}])
|
230
|
-
item
|
231
|
-
template
|
232
|
-
collection
|
233
|
-
get '/posts'
|
234
|
-
end
|
235
|
-
|
236
|
-
it 'renders collection' do
|
237
|
-
expect(last_response.body).to eq(MultiJson.dump(app.collection(:posts), mode: :compat))
|
238
|
-
end
|
239
|
-
|
240
|
-
it 'renders items' do
|
241
|
-
body = MultiJson.load(last_response.body)
|
242
|
-
|
243
|
-
expect(body['items'][0]).to eq({
|
244
|
-
'href' => '/posts/1234',
|
245
|
-
|
246
|
-
'links' => [],
|
247
|
-
|
248
|
-
'data' => [
|
249
|
-
{ 'name' => 'title', 'value' => 'My Test', 'prompt' => 'Title' }
|
250
|
-
]
|
251
|
-
})
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
context 'withouth existing collection' do
|
256
|
-
before do
|
257
|
-
template
|
258
|
-
collection
|
259
|
-
get '/fakes'
|
260
|
-
end
|
261
|
-
|
262
|
-
it 'renders 404' do
|
263
|
-
expect(last_response.status).to eq(404)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
def item
|
268
|
-
app.item :post do |i|
|
269
|
-
i.href = '/posts/{id}'
|
270
|
-
|
271
|
-
i.data = [
|
272
|
-
{ name: 'title', value: '{title}', prompt: 'Title' },
|
273
|
-
]
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def template
|
278
|
-
app.template :post do |t|
|
279
|
-
t.data = [
|
280
|
-
{ name: 'title', value: '', prompt: 'Title' },
|
281
|
-
{ name: 'body', value: '', prompt: 'Body' }
|
282
|
-
]
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
def collection
|
287
|
-
app.collection :posts do |c|
|
288
|
-
c.version = '1.0'
|
289
|
-
c.href = '/posts'
|
290
|
-
|
291
|
-
c.template = app.template(:post)
|
292
|
-
end
|
293
|
-
end
|
294
|
-
end
|