hypa 0.0.1 → 0.0.2
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 +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
|
-
[] [](https://travis-ci.org/nmerouze/hypa) [](https://codeclimate.com/github/nmerouze/hypa) [](https://gemnasium.com/nmerouze/hypa) [](https://coveralls.io/r/nmerouze/hypa)
|
3
|
+
[](http://rubygems.org/gems/hypa) [](https://travis-ci.org/nmerouze/hypa) [](https://codeclimate.com/github/nmerouze/hypa) [](https://gemnasium.com/nmerouze/hypa) [](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
|