her 0.5.3 → 0.5.4
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 +8 -8
- data/.gitignore +2 -2
- data/.rspec +1 -2
- data/.travis.yml +2 -2
- data/README.md +10 -16
- data/UPGRADE.md +4 -0
- data/examples/grape-and-her/.env.default +3 -0
- data/examples/grape-and-her/Procfile +2 -0
- data/examples/grape-and-her/README.md +27 -0
- data/examples/grape-and-her/api/Gemfile +11 -0
- data/examples/grape-and-her/api/Rakefile +14 -0
- data/examples/grape-and-her/api/app/api.rb +49 -0
- data/examples/grape-and-her/api/app/models/organization.rb +7 -0
- data/examples/grape-and-her/api/app/models/user.rb +9 -0
- data/examples/grape-and-her/api/app/views/organizations/_base.rabl +2 -0
- data/examples/grape-and-her/api/app/views/organizations/index.rabl +3 -0
- data/examples/grape-and-her/api/app/views/organizations/show.rabl +3 -0
- data/examples/grape-and-her/api/app/views/users/_base.rabl +8 -0
- data/examples/grape-and-her/api/app/views/users/index.rabl +3 -0
- data/examples/grape-and-her/api/app/views/users/show.rabl +3 -0
- data/examples/grape-and-her/api/config.ru +5 -0
- data/examples/grape-and-her/api/config/boot.rb +17 -0
- data/examples/grape-and-her/api/config/unicorn.rb +7 -0
- data/examples/grape-and-her/api/db/migrations/001_create_users.rb +11 -0
- data/examples/grape-and-her/api/db/migrations/002_create_organizations.rb +8 -0
- data/examples/grape-and-her/consumer/Gemfile +23 -0
- data/examples/grape-and-her/consumer/app/assets/stylesheets/application.scss +190 -0
- data/examples/grape-and-her/consumer/app/assets/stylesheets/reset.scss +53 -0
- data/examples/grape-and-her/consumer/app/consumer.rb +74 -0
- data/examples/grape-and-her/consumer/app/models/organization.rb +13 -0
- data/examples/grape-and-her/consumer/app/models/user.rb +13 -0
- data/examples/grape-and-her/consumer/app/views/index.haml +9 -0
- data/examples/grape-and-her/consumer/app/views/layout.haml +20 -0
- data/examples/grape-and-her/consumer/app/views/organizations/index.haml +25 -0
- data/examples/grape-and-her/consumer/app/views/organizations/show.haml +11 -0
- data/examples/grape-and-her/consumer/app/views/users/index.haml +33 -0
- data/examples/grape-and-her/consumer/app/views/users/show.haml +9 -0
- data/examples/grape-and-her/consumer/config.ru +20 -0
- data/examples/grape-and-her/consumer/config/boot.rb +30 -0
- data/examples/grape-and-her/consumer/config/unicorn.rb +7 -0
- data/examples/grape-and-her/consumer/lib/response_logger.rb +18 -0
- data/her.gemspec +2 -2
- data/lib/her/model.rb +22 -26
- data/lib/her/model/associations.rb +19 -19
- data/lib/her/model/attributes.rb +173 -0
- data/lib/her/model/base.rb +17 -0
- data/lib/her/model/http.rb +58 -242
- data/lib/her/model/introspection.rb +7 -8
- data/lib/her/model/nested_attributes.rb +3 -3
- data/lib/her/model/orm.rb +15 -205
- data/lib/her/model/parse.rb +86 -0
- data/lib/her/model/paths.rb +54 -14
- data/lib/her/version.rb +1 -1
- data/spec/model/attributes_spec.rb +139 -0
- data/spec/model/dirty_spec.rb +40 -0
- data/spec/model/introspection_spec.rb +5 -5
- data/spec/model/orm_spec.rb +14 -128
- data/spec/model/paths_spec.rb +26 -0
- data/spec/model/validations_spec.rb +17 -0
- data/spec/spec_helper.rb +7 -32
- data/spec/support/extensions/array.rb +5 -0
- data/spec/support/extensions/hash.rb +5 -0
- data/spec/support/macros/model_macros.rb +29 -0
- metadata +52 -15
- data/examples/twitter-oauth/Gemfile +0 -13
- data/examples/twitter-oauth/app.rb +0 -50
- data/examples/twitter-oauth/config.ru +0 -5
- data/examples/twitter-oauth/views/index.haml +0 -9
- data/examples/twitter-search/Gemfile +0 -12
- data/examples/twitter-search/app.rb +0 -55
- data/examples/twitter-search/config.ru +0 -5
- data/examples/twitter-search/views/index.haml +0 -9
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MmJlNGEzMjNhZjMyOTYyZWVkYmM2NjhhNGEwZDAwZmY0ODk4MWRkNQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OGVjNGUzNDY5YTQxZmQzMzgxMDJmMWM4OGUxNmRhMjE1NWY2YzQ3Mw==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YmQ0Y2VkYTM2YTg1ZmRkNzBmODkxMGVhMDI3NTI2YzRkZmYwZTJjMzE5OTI3
|
10
|
+
Nzg0ZjRiMzU3ZTEzMTY5YTBiMjk4YjYzYzYzMWMwNjFlMmU2MzI3MTdhZDQ5
|
11
|
+
OGUyODNhMTc4YTA3ZDYzYjVlOTc5ZGY1Y2UzZTk4MzJlM2U0YzI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
NzVmNzU1OWVlNjQ0NzNlNWM2MmVjZDc2NjU5OWQ5NWJlN2I0ZWRkZmY3Y2Q4
|
14
|
+
MTI1YzkyMDJhNDM2ZDBmN2ZmNmE4MzQxODc5MzhmZDEwNGFjOTE4MzFmYzA4
|
15
|
+
OTY3OGZlNjM5MDMxZGFhMzExZDRjZTRmOTNkMjZkYWFkOTA4Nzc=
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
--
|
2
|
-
--format=documentation
|
1
|
+
--colour --format=Fivemat
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -92,7 +92,7 @@ user = User.new(:fullname => "Maeby Fünke")
|
|
92
92
|
user.save
|
93
93
|
```
|
94
94
|
|
95
|
-
You can look into the `examples` directory for sample
|
95
|
+
You can look into the `examples` directory for a sample application using Her. For a complete reference of all the methods you can use, check out [the documentation](http://rdoc.info/github/remiprev/her).
|
96
96
|
|
97
97
|
## Middleware
|
98
98
|
|
@@ -102,31 +102,23 @@ Since Her relies on [Faraday](https://github.com/technoweenie/faraday) to send H
|
|
102
102
|
|
103
103
|
Her doesn’t support authentication by default. However, it’s easy to implement one with request middleware. Using the `connection` block, we can add it to the middleware stack.
|
104
104
|
|
105
|
-
For example, to add a
|
105
|
+
For example, to add a token header to your API requests in a Rails application, you could use the excellent [`request_store`](https://rubygems.org/gems/request_store) gem like this:
|
106
106
|
|
107
107
|
```ruby
|
108
108
|
# app/controllers/application_controller.rb
|
109
109
|
class ApplicationController < ActionController::Base
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
yield
|
116
|
-
ensure
|
117
|
-
Thread.current[:my_access_token] = nil
|
118
|
-
end
|
110
|
+
before_filter :set_user_api_token
|
111
|
+
|
112
|
+
protected
|
113
|
+
def set_user_api_token
|
114
|
+
RequestStore.store[:my_api_token] = current_user.api_token # or something similar based on `session`
|
119
115
|
end
|
120
116
|
end
|
121
117
|
|
122
118
|
# lib/my_token_authentication.rb
|
123
119
|
class MyTokenAuthentication < Faraday::Middleware
|
124
|
-
def initialize(app, options={})
|
125
|
-
@app = app
|
126
|
-
end
|
127
|
-
|
128
120
|
def call(env)
|
129
|
-
env[:request_headers]["X-API-Token"] =
|
121
|
+
env[:request_headers]["X-API-Token"] = RequestStore.store[:my_api_token]
|
130
122
|
@app.call(env)
|
131
123
|
end
|
132
124
|
end
|
@@ -136,6 +128,7 @@ require "lib/my_token_authentication"
|
|
136
128
|
|
137
129
|
Her::API.setup :url => "https://api.example.com" do |connection|
|
138
130
|
connection.use MyTokenAuthentication
|
131
|
+
connection.use Faraday::Request::UrlEncoded
|
139
132
|
connection.use Her::Middleware::DefaultParseJSON
|
140
133
|
connection.use Faraday::Adapter::NetHttp
|
141
134
|
end
|
@@ -781,6 +774,7 @@ These fine folks helped with Her:
|
|
781
774
|
* [@joanniclaborde](https://github.com/joanniclaborde)
|
782
775
|
* [@seanreads](https://github.com/seanreads)
|
783
776
|
* [@jonkarna](https://github.com/jonkarna)
|
777
|
+
* [@aclevy](https://github.com/aclevy)
|
784
778
|
|
785
779
|
## License
|
786
780
|
|
data/UPGRADE.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
Here is a list of backward-incompatible changes that were introduced while Her is pre-1.0. After reaching 1.0, it will follow the [Semantic Versioning](http://semver.org/) system.
|
4
4
|
|
5
|
+
## 0.5.4
|
6
|
+
|
7
|
+
* Her does not support Ruby 1.8.7 anymore. You should upgrade to 1.9.2, 1.9.3 or 2.0.0.
|
8
|
+
|
5
9
|
## 0.5
|
6
10
|
|
7
11
|
* Her is now compatible with `ActiveModel` and includes `ActiveModel::Validations`.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Grape + Her example
|
2
|
+
|
3
|
+
This is an example of how to use Her to consume a simple API. It consists of two separate applications, a REST API (powered by `grape` and `activerecord`) and a consumer application (powered by `sinatra` and `her`).
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
## Installation and Usage
|
8
|
+
|
9
|
+
```shell
|
10
|
+
# Clone the repository
|
11
|
+
$ git clone git://github.com/remiprev/her.git
|
12
|
+
|
13
|
+
# Go to the example directory
|
14
|
+
$ cd her/examples/grape-and-her
|
15
|
+
|
16
|
+
# Go to each application and run `bundle install`
|
17
|
+
$ cd api; bundle install; cd ..
|
18
|
+
$ cd consumer; bundle install; cd ..
|
19
|
+
|
20
|
+
# Create the API database
|
21
|
+
$ cd api; sqlite3 db/development.db ""; bundle exec rake db:migrate; cd ..
|
22
|
+
|
23
|
+
# Start foreman with the Procfile
|
24
|
+
$ foreman start
|
25
|
+
```
|
26
|
+
|
27
|
+
This should start the API on `http://0.0.0.0:3100` and the consumer on `http://0.0.0.0:3200`
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'portugal/tasks'
|
2
|
+
Portugal.configure do |config|
|
3
|
+
config.migrations_path = File.expand_path("../db/migrations", __FILE__)
|
4
|
+
end
|
5
|
+
|
6
|
+
# This task is called before Portugal executes its tasks.
|
7
|
+
# It should require ActiveRecord somehow and establish the database connection
|
8
|
+
#
|
9
|
+
# Change it so it works for your application
|
10
|
+
task :environment do
|
11
|
+
require 'bundler'
|
12
|
+
Bundler.require
|
13
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: File.expand_path('../db/development.db', __FILE__)
|
14
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class API < Grape::API
|
2
|
+
content_type :json, "application/json; charset=UTF-8"
|
3
|
+
format :json
|
4
|
+
formatter :json, Grape::Formatter::Rabl
|
5
|
+
use(Rack::Config) { |env| env['api.tilt.root'] = File.expand_path('../views', __FILE__) }
|
6
|
+
rescue_from :all
|
7
|
+
|
8
|
+
resources :users do
|
9
|
+
desc 'Return all users'
|
10
|
+
get nil, :rabl => "users/index" do
|
11
|
+
@users = User.all
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Return a specific user'
|
15
|
+
get ':id', :rabl => "users/show" do
|
16
|
+
@user = User.find(params[:id])
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Create a new user'
|
20
|
+
post nil, :rabl => "users/show" do
|
21
|
+
@user = User.new(params[:user])
|
22
|
+
error!({ :errors => @user.errors.full_messages }, 400) unless @user.save
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
resources :organizations do
|
27
|
+
desc 'Return all organizations'
|
28
|
+
get nil, :rabl => "organizations/index" do
|
29
|
+
@organizations = Organization.all
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Return a specific organization'
|
33
|
+
get ':id', :rabl => "organizations/show" do
|
34
|
+
@organization = Organization.find(params[:id])
|
35
|
+
end
|
36
|
+
|
37
|
+
desc 'Return all users for specific organization'
|
38
|
+
get ':id/users', :rabl => "users/index" do
|
39
|
+
organization = Organization.find(params[:id])
|
40
|
+
@users = organization.users
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Create a new organization'
|
44
|
+
post nil, :rabl => "organizations/show" do
|
45
|
+
@organization = Organization.new(params[:organization])
|
46
|
+
error!({ :errors => @organization.errors.full_messages }, 400) unless @organization.save
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Bundler setup
|
2
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
3
|
+
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
|
4
|
+
Bundler.require(:default, ENV['RACK_ENV']) if defined? Bundler
|
5
|
+
|
6
|
+
# Configure ActiveRecord
|
7
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: File.expand_path('../../db/development.db', __FILE__)
|
8
|
+
|
9
|
+
# Require models
|
10
|
+
Dir[File.expand_path('../../app/models/**/*.rb', __FILE__)].each do |file|
|
11
|
+
dirname = File.dirname(file)
|
12
|
+
file_basename = File.basename(file, File.extname(file))
|
13
|
+
require "#{dirname}/#{file_basename}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Application setup
|
17
|
+
require File.expand_path('../../app/api', __FILE__)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Server
|
4
|
+
gem 'unicorn'
|
5
|
+
|
6
|
+
# Web
|
7
|
+
gem 'sinatra'
|
8
|
+
gem 'sinatra-reloader'
|
9
|
+
gem 'haml'
|
10
|
+
gem 'her'
|
11
|
+
gem 'multi_json'
|
12
|
+
|
13
|
+
# Assets
|
14
|
+
gem 'sass'
|
15
|
+
gem 'sprockets'
|
16
|
+
gem 'sprockets-sass'
|
17
|
+
gem 'sprockets-helpers'
|
18
|
+
|
19
|
+
group :development do
|
20
|
+
gem 'pry'
|
21
|
+
gem 'rake'
|
22
|
+
gem 'foreman'
|
23
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
@import "reset.css";
|
2
|
+
|
3
|
+
* {
|
4
|
+
-webkit-box-sizing: border-box;
|
5
|
+
-moz-box-sizing: border-box;
|
6
|
+
box-sizing: border-box;
|
7
|
+
}
|
8
|
+
|
9
|
+
html {
|
10
|
+
font-size: 62.5%;
|
11
|
+
}
|
12
|
+
|
13
|
+
body {
|
14
|
+
background: #eee;
|
15
|
+
color: #000;
|
16
|
+
padding: 30px;
|
17
|
+
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
18
|
+
font-size: 150%;
|
19
|
+
}
|
20
|
+
|
21
|
+
input, textarea, select, option, button {
|
22
|
+
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
23
|
+
font-size: 100%;
|
24
|
+
}
|
25
|
+
|
26
|
+
hr {
|
27
|
+
display: none;
|
28
|
+
}
|
29
|
+
|
30
|
+
a {
|
31
|
+
color: #1b49ff;
|
32
|
+
}
|
33
|
+
|
34
|
+
#wrap {
|
35
|
+
background: #fff;
|
36
|
+
box-shadow: 0 0 20px rgba(0,0,0,0.3);
|
37
|
+
border: 1px solid #bbb;
|
38
|
+
border-radius: 4px;
|
39
|
+
overflow:hidden;
|
40
|
+
}
|
41
|
+
|
42
|
+
#main-header {
|
43
|
+
font-size: 130%;
|
44
|
+
background: #ddd;
|
45
|
+
|
46
|
+
strong {
|
47
|
+
font-family: Pacifico, serif;
|
48
|
+
font-size: 250%;
|
49
|
+
font-weight: normal;
|
50
|
+
display: inline-block;
|
51
|
+
vertical-align: middle;
|
52
|
+
}
|
53
|
+
|
54
|
+
span {
|
55
|
+
display: inline-block;
|
56
|
+
vertical-align: middle;
|
57
|
+
color: rgba(0,0,0,0.4);
|
58
|
+
font-weight: normal;
|
59
|
+
margin: 0 0 0 20px;
|
60
|
+
font-size: 80%;
|
61
|
+
}
|
62
|
+
|
63
|
+
a {
|
64
|
+
display: block;
|
65
|
+
padding: 30px 20px;
|
66
|
+
color: #000;
|
67
|
+
text-decoration: none;
|
68
|
+
|
69
|
+
&:hover {
|
70
|
+
background: rgba(0,0,0,0.05);
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
#main-footer {
|
76
|
+
pre {
|
77
|
+
padding: 20px;
|
78
|
+
color: rgba(0,0,0,0.75);
|
79
|
+
line-height: 1.4;
|
80
|
+
font-size: 80%;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
#content {
|
85
|
+
padding: 30px;
|
86
|
+
|
87
|
+
h1 {
|
88
|
+
margin: 25px 0 15px;
|
89
|
+
font-size: 150%;
|
90
|
+
&:first-child { margin-top: 0; }
|
91
|
+
}
|
92
|
+
|
93
|
+
h2 {
|
94
|
+
margin: 25px 0 15px;
|
95
|
+
font-size: 120%;
|
96
|
+
&:first-child { margin-top: 0; }
|
97
|
+
}
|
98
|
+
|
99
|
+
h3 {
|
100
|
+
margin: 25px 0 15px;
|
101
|
+
font-size: 110%;
|
102
|
+
&:first-child { margin-top: 0; }
|
103
|
+
}
|
104
|
+
|
105
|
+
pre {
|
106
|
+
background: #f9ffeb;
|
107
|
+
border: 1px solid rgba(0,0,0,0.15);
|
108
|
+
padding: 15px;
|
109
|
+
}
|
110
|
+
|
111
|
+
form {
|
112
|
+
margin: 0 0 15px;
|
113
|
+
padding: 0 0 15px;
|
114
|
+
border-bottom: 1px solid #ddd;
|
115
|
+
|
116
|
+
p {
|
117
|
+
margin: 0 0 15px;
|
118
|
+
}
|
119
|
+
|
120
|
+
label {
|
121
|
+
display: block;
|
122
|
+
margin: 0 0 6px;
|
123
|
+
cursor: pointer;
|
124
|
+
}
|
125
|
+
|
126
|
+
input[type=text], input[type=email] {
|
127
|
+
padding: 10px;
|
128
|
+
width: 100%;
|
129
|
+
border: 1px solid #ccc;
|
130
|
+
border-radius: 2px;
|
131
|
+
}
|
132
|
+
|
133
|
+
button {
|
134
|
+
cursor: pointer;
|
135
|
+
background: #1b49ff;
|
136
|
+
color: #fff;
|
137
|
+
padding: 8px 11px;
|
138
|
+
border-radius: 3px;
|
139
|
+
border: 1px solid rgba(0,0,0,0.3);
|
140
|
+
}
|
141
|
+
|
142
|
+
.errors {
|
143
|
+
padding: 15px;
|
144
|
+
background: #fcc;
|
145
|
+
border: 1px solid rgba(0,0,0,0.2);
|
146
|
+
margin: 0 0 20px;
|
147
|
+
font-size: 90%;
|
148
|
+
color: #900;
|
149
|
+
|
150
|
+
li {
|
151
|
+
margin: 0 0 5px 20px;
|
152
|
+
list-style: disc;
|
153
|
+
&:last-child { margin-bottom: 0; }
|
154
|
+
}
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
.records {
|
159
|
+
li {
|
160
|
+
list-style: disc;
|
161
|
+
margin: 0 0 10px 20px;
|
162
|
+
|
163
|
+
&:last-child { margin-bottom: 0; }
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
.no-records {
|
168
|
+
color: rgba(0,0,0,0.8);
|
169
|
+
font-style: italic;
|
170
|
+
font-size: 80%;
|
171
|
+
}
|
172
|
+
|
173
|
+
.details {
|
174
|
+
margin: 0 0 20px;
|
175
|
+
|
176
|
+
li {
|
177
|
+
list-style: disc;
|
178
|
+
margin: 0 0 6px 20px;
|
179
|
+
|
180
|
+
&:last-child { margin-bottom: 0; }
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
.back {
|
185
|
+
border-top: 1px solid #ddd;
|
186
|
+
padding: 15px 0 0;
|
187
|
+
margin: 30px 0 0;
|
188
|
+
font-size: 85%;
|
189
|
+
}
|
190
|
+
}
|