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
@@ -0,0 +1,53 @@
|
|
1
|
+
/* http://meyerweb.com/eric/tools/css/reset/ */
|
2
|
+
/* v1.0 | 20080212 */
|
3
|
+
|
4
|
+
html, body, div, span, applet, object, iframe,
|
5
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
6
|
+
a, abbr, acronym, address, big, cite, code,
|
7
|
+
del, dfn, em, font, img, ins, kbd, q, s, samp,
|
8
|
+
small, strike, strong, sub, sup, tt, var,
|
9
|
+
b, u, i, center,
|
10
|
+
dl, dt, dd, ol, ul, li,
|
11
|
+
fieldset, form, label, legend,
|
12
|
+
table, caption, tbody, tfoot, thead, tr, th, td {
|
13
|
+
margin: 0;
|
14
|
+
padding: 0;
|
15
|
+
border: 0;
|
16
|
+
outline: 0;
|
17
|
+
font-size: 100%;
|
18
|
+
vertical-align: baseline;
|
19
|
+
background: transparent;
|
20
|
+
}
|
21
|
+
body {
|
22
|
+
line-height: 1;
|
23
|
+
}
|
24
|
+
ol, ul {
|
25
|
+
list-style: none;
|
26
|
+
}
|
27
|
+
blockquote, q {
|
28
|
+
quotes: none;
|
29
|
+
}
|
30
|
+
blockquote:before, blockquote:after,
|
31
|
+
q:before, q:after {
|
32
|
+
content: '';
|
33
|
+
content: none;
|
34
|
+
}
|
35
|
+
|
36
|
+
/* remember to define focus styles! */
|
37
|
+
:focus {
|
38
|
+
outline: 0;
|
39
|
+
}
|
40
|
+
|
41
|
+
/* remember to highlight inserts somehow! */
|
42
|
+
ins {
|
43
|
+
text-decoration: none;
|
44
|
+
}
|
45
|
+
del {
|
46
|
+
text-decoration: line-through;
|
47
|
+
}
|
48
|
+
|
49
|
+
/* tables still need 'cellspacing="0"' in the markup */
|
50
|
+
table {
|
51
|
+
border-collapse: collapse;
|
52
|
+
border-spacing: 0;
|
53
|
+
}
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class Consumer < Sinatra::Base
|
2
|
+
configure do
|
3
|
+
set :root, -> { File.expand_path("./") }
|
4
|
+
set :views, -> { File.join(root, "app/views") }
|
5
|
+
set :haml, :format => :html5, :attr_wrapper => '"', :ugly => true
|
6
|
+
end
|
7
|
+
|
8
|
+
configure :development do
|
9
|
+
register Sinatra::Reloader
|
10
|
+
end
|
11
|
+
|
12
|
+
helpers Sprockets::Helpers
|
13
|
+
|
14
|
+
before do
|
15
|
+
$strio.truncate(0)
|
16
|
+
end
|
17
|
+
|
18
|
+
# GET /
|
19
|
+
get '/' do
|
20
|
+
haml :index
|
21
|
+
end
|
22
|
+
|
23
|
+
# GET /users
|
24
|
+
get '/users' do
|
25
|
+
@users = User.all
|
26
|
+
@user = User.new
|
27
|
+
|
28
|
+
haml :'users/index'
|
29
|
+
end
|
30
|
+
|
31
|
+
# GET /users/:id
|
32
|
+
get '/users/:id' do
|
33
|
+
@user = User.find(params[:id])
|
34
|
+
haml :'users/show'
|
35
|
+
end
|
36
|
+
|
37
|
+
# GET /post
|
38
|
+
post '/users' do
|
39
|
+
@users = User.all
|
40
|
+
@user = User.new(params[:user])
|
41
|
+
|
42
|
+
if @user.save
|
43
|
+
redirect to('/users')
|
44
|
+
else
|
45
|
+
haml :'users/index'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# GET /organizations
|
50
|
+
get '/organizations' do
|
51
|
+
@organizations = Organization.all
|
52
|
+
@organization = Organization.new
|
53
|
+
|
54
|
+
haml :'organizations/index'
|
55
|
+
end
|
56
|
+
|
57
|
+
# GET /organizations/:id
|
58
|
+
get '/organizations/:id' do
|
59
|
+
@organization = Organization.find(params[:id])
|
60
|
+
haml :'organizations/show'
|
61
|
+
end
|
62
|
+
|
63
|
+
# GET /post
|
64
|
+
post '/organizations' do
|
65
|
+
@organizations = Organization.all
|
66
|
+
@organization = Organization.new(params[:organization])
|
67
|
+
|
68
|
+
if @organization.save
|
69
|
+
redirect to('/organizations')
|
70
|
+
else
|
71
|
+
haml :'organizations/index'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
!!! 5
|
2
|
+
%head
|
3
|
+
%title Grape + Her example
|
4
|
+
%link{ rel: "stylesheet", type: "text/css", href: "http://fonts.googleapis.com/css?family=Pacifico" }
|
5
|
+
%link{ type: "text/css", rel: "stylesheet", media: 'screen', href: stylesheet_path("application") }
|
6
|
+
%body
|
7
|
+
#wrap
|
8
|
+
%header#main-header
|
9
|
+
%h1
|
10
|
+
%a{ href: '/' }
|
11
|
+
%strong
|
12
|
+
Her
|
13
|
+
%span
|
14
|
+
Grape + Her example
|
15
|
+
%hr
|
16
|
+
#content
|
17
|
+
= yield
|
18
|
+
%footer#main-footer
|
19
|
+
%pre
|
20
|
+
= $strio.string
|
@@ -0,0 +1,25 @@
|
|
1
|
+
%h2 Create new organization
|
2
|
+
|
3
|
+
%form{ url: "/organizations", method: 'post' }
|
4
|
+
- if @organization.response_errors.any?
|
5
|
+
%ul.errors
|
6
|
+
%p The organization could not be created:
|
7
|
+
%ul
|
8
|
+
- @organization.response_errors.each do |error|
|
9
|
+
%li= error
|
10
|
+
|
11
|
+
%p
|
12
|
+
%label{ for: 'name' } Name
|
13
|
+
%input{ type: 'text', name: 'organization[name]', value: @organization.name, id: 'name' }
|
14
|
+
%p
|
15
|
+
%button{ type: 'submit' } Create
|
16
|
+
|
17
|
+
%hr
|
18
|
+
%h3 Current organizations
|
19
|
+
- if @organizations.any?
|
20
|
+
%ul.records
|
21
|
+
- @organizations.each do |organization|
|
22
|
+
%li
|
23
|
+
%a{ href: "/organizations/#{organization.id}" }= organization.name
|
24
|
+
- else
|
25
|
+
%p.no-records There are currently no organizations.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
%h2 Create new user
|
2
|
+
|
3
|
+
%form{ url: "/users", method: 'post' }
|
4
|
+
- if @user.response_errors.any?
|
5
|
+
.errors
|
6
|
+
%p The user could not be created:
|
7
|
+
%ul
|
8
|
+
- @user.response_errors.each do |error|
|
9
|
+
%li= error
|
10
|
+
|
11
|
+
%p
|
12
|
+
%label{ for: 'email' } Email
|
13
|
+
%input{ type: 'email', name: 'user[email]', value: @user.email, id: 'email' }
|
14
|
+
%p
|
15
|
+
%label{ for: 'fullname' } Full Name
|
16
|
+
%input{ type: 'text', name: 'user[fullname]', value: @user.fullname, id: 'fullname' }
|
17
|
+
%p
|
18
|
+
%label{ for: 'organization_id' } Organization
|
19
|
+
%select{ name: 'user[organization_id]', id: 'organization_id' }
|
20
|
+
- Organization.all.each do |organization|
|
21
|
+
%option{ value: organization.id }= organization.name
|
22
|
+
%p
|
23
|
+
%button{ type: 'submit' } Create
|
24
|
+
|
25
|
+
%hr
|
26
|
+
%h3 Current users
|
27
|
+
- if @users.any?
|
28
|
+
%ul.records
|
29
|
+
- @users.each do |user|
|
30
|
+
%li
|
31
|
+
%a{ href: "/users/#{user.id}" }= user.fullname
|
32
|
+
- else
|
33
|
+
%p.no-records There are currently no users.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path('../config/boot', __FILE__)
|
2
|
+
|
3
|
+
map "/assets" do
|
4
|
+
sprockets = Sprockets::Environment.new.tap do |s|
|
5
|
+
s.append_path File.join(File.dirname(__FILE__), 'app', 'assets', 'stylesheets')
|
6
|
+
s.append_path File.join(File.dirname(__FILE__), 'app', 'assets', 'images')
|
7
|
+
|
8
|
+
Sprockets::Helpers.configure do |config|
|
9
|
+
config.environment = s
|
10
|
+
config.prefix = "/assets"
|
11
|
+
config.digest = true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
run sprockets
|
16
|
+
end
|
17
|
+
|
18
|
+
map "/" do
|
19
|
+
run Consumer
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
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
|
+
# Require libraries
|
7
|
+
Dir[File.expand_path('../../lib/**/*.rb', __FILE__)].each do |file|
|
8
|
+
dirname = File.dirname(file)
|
9
|
+
file_basename = File.basename(file, File.extname(file))
|
10
|
+
require "#{dirname}/#{file_basename}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Configure Her
|
14
|
+
Her::API.setup :url => "http://0.0.0.0:3100" do |c|
|
15
|
+
c.use Faraday::Request::UrlEncoded
|
16
|
+
c.use Her::Middleware::DefaultParseJSON
|
17
|
+
c.use ResponseBodyLogger, $logger
|
18
|
+
c.use Faraday::Response::Logger, $logger
|
19
|
+
c.use Faraday::Adapter::NetHttp
|
20
|
+
end
|
21
|
+
|
22
|
+
# Require models
|
23
|
+
Dir[File.expand_path('../../app/models/**/*.rb', __FILE__)].each do |file|
|
24
|
+
dirname = File.dirname(file)
|
25
|
+
file_basename = File.basename(file, File.extname(file))
|
26
|
+
require "#{dirname}/#{file_basename}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Application setup
|
30
|
+
require File.expand_path('../../app/consumer', __FILE__)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
class ResponseBodyLogger < Faraday::Response::Middleware
|
5
|
+
def initialize(app, logger)
|
6
|
+
@app = app
|
7
|
+
@logger = logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_complete(env)
|
11
|
+
@logger.info ""
|
12
|
+
@logger.info env[:body]
|
13
|
+
@logger.info "\n"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
$strio = StringIO.new
|
18
|
+
$logger = Logger.new($strio).tap { |logger| logger.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" } }
|
data/her.gemspec
CHANGED
@@ -19,10 +19,10 @@ Gem::Specification.new do |s|
|
|
19
19
|
|
20
20
|
s.add_development_dependency "rake", "~> 10.0"
|
21
21
|
s.add_development_dependency "rspec", "~> 2.13"
|
22
|
-
s.add_development_dependency "
|
22
|
+
s.add_development_dependency "fivemat", "~> 1.2"
|
23
23
|
|
24
24
|
s.add_runtime_dependency "activemodel", ">= 3.0.0"
|
25
25
|
s.add_runtime_dependency "activesupport", ">= 3.0.0"
|
26
26
|
s.add_runtime_dependency "faraday", "~> 0.8"
|
27
|
-
s.add_runtime_dependency "multi_json", "~> 1.
|
27
|
+
s.add_runtime_dependency "multi_json", "~> 1.7"
|
28
28
|
end
|
data/lib/her/model.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require "her/model/base"
|
2
2
|
require "her/model/http"
|
3
|
+
require "her/model/attributes"
|
3
4
|
require "her/model/orm"
|
5
|
+
require "her/model/parse"
|
4
6
|
require "her/model/associations"
|
5
7
|
require "her/model/introspection"
|
6
8
|
require "her/model/paths"
|
@@ -21,48 +23,42 @@ module Her
|
|
21
23
|
module Model
|
22
24
|
extend ActiveSupport::Concern
|
23
25
|
|
24
|
-
#
|
26
|
+
# Her modules
|
27
|
+
include Her::Model::Base
|
28
|
+
include Her::Model::Attributes
|
25
29
|
include Her::Model::ORM
|
30
|
+
include Her::Model::HTTP
|
31
|
+
include Her::Model::Parse
|
26
32
|
include Her::Model::Introspection
|
27
33
|
include Her::Model::Paths
|
28
34
|
include Her::Model::Associations
|
29
35
|
include Her::Model::NestedAttributes
|
36
|
+
|
37
|
+
# Supported ActiveModel modules
|
30
38
|
include ActiveModel::Validations
|
31
39
|
include ActiveModel::Conversion
|
32
40
|
include ActiveModel::Dirty
|
41
|
+
include ActiveModel::Naming
|
42
|
+
include ActiveModel::Translation
|
33
43
|
|
34
44
|
# Class methods
|
35
45
|
included do
|
36
|
-
|
37
|
-
|
38
|
-
extend ActiveModel::Naming
|
39
|
-
extend ActiveModel::Translation
|
46
|
+
# Define the root element name, used when `parse_root_in_json` is set to `true`
|
47
|
+
root_element self.name.split("::").last.underscore.to_sym
|
40
48
|
|
41
|
-
|
42
|
-
|
49
|
+
# Define resource and collection paths
|
50
|
+
collection_path "#{root_element.to_s.pluralize}"
|
51
|
+
resource_path "#{root_element.to_s.pluralize}/:id"
|
43
52
|
|
44
|
-
#
|
45
|
-
root_element self.name.split("::").last.underscore
|
46
|
-
base_path = root_element.pluralize
|
47
|
-
collection_path "#{base_path}"
|
48
|
-
resource_path "#{base_path}/:id"
|
53
|
+
# Assign the default API
|
49
54
|
uses_api Her::API.default_api
|
50
|
-
end
|
51
55
|
|
52
|
-
|
53
|
-
|
54
|
-
# * an association
|
55
|
-
def has_key?(attribute_name)
|
56
|
-
has_data?(attribute_name) ||
|
57
|
-
has_association?(attribute_name)
|
58
|
-
end
|
56
|
+
# Define the default primary key
|
57
|
+
primary_key :id
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
def [](attribute_name)
|
64
|
-
get_data(attribute_name) ||
|
65
|
-
get_association(attribute_name)
|
59
|
+
# Configure ActiveModel callbacks
|
60
|
+
extend ActiveModel::Callbacks
|
61
|
+
define_model_callbacks :create, :update, :save, :find, :destroy
|
66
62
|
end
|
67
63
|
end
|
68
64
|
end
|