her 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +2 -2
  3. data/.rspec +1 -2
  4. data/.travis.yml +2 -2
  5. data/README.md +10 -16
  6. data/UPGRADE.md +4 -0
  7. data/examples/grape-and-her/.env.default +3 -0
  8. data/examples/grape-and-her/Procfile +2 -0
  9. data/examples/grape-and-her/README.md +27 -0
  10. data/examples/grape-and-her/api/Gemfile +11 -0
  11. data/examples/grape-and-her/api/Rakefile +14 -0
  12. data/examples/grape-and-her/api/app/api.rb +49 -0
  13. data/examples/grape-and-her/api/app/models/organization.rb +7 -0
  14. data/examples/grape-and-her/api/app/models/user.rb +9 -0
  15. data/examples/grape-and-her/api/app/views/organizations/_base.rabl +2 -0
  16. data/examples/grape-and-her/api/app/views/organizations/index.rabl +3 -0
  17. data/examples/grape-and-her/api/app/views/organizations/show.rabl +3 -0
  18. data/examples/grape-and-her/api/app/views/users/_base.rabl +8 -0
  19. data/examples/grape-and-her/api/app/views/users/index.rabl +3 -0
  20. data/examples/grape-and-her/api/app/views/users/show.rabl +3 -0
  21. data/examples/grape-and-her/api/config.ru +5 -0
  22. data/examples/grape-and-her/api/config/boot.rb +17 -0
  23. data/examples/grape-and-her/api/config/unicorn.rb +7 -0
  24. data/examples/grape-and-her/api/db/migrations/001_create_users.rb +11 -0
  25. data/examples/grape-and-her/api/db/migrations/002_create_organizations.rb +8 -0
  26. data/examples/grape-and-her/consumer/Gemfile +23 -0
  27. data/examples/grape-and-her/consumer/app/assets/stylesheets/application.scss +190 -0
  28. data/examples/grape-and-her/consumer/app/assets/stylesheets/reset.scss +53 -0
  29. data/examples/grape-and-her/consumer/app/consumer.rb +74 -0
  30. data/examples/grape-and-her/consumer/app/models/organization.rb +13 -0
  31. data/examples/grape-and-her/consumer/app/models/user.rb +13 -0
  32. data/examples/grape-and-her/consumer/app/views/index.haml +9 -0
  33. data/examples/grape-and-her/consumer/app/views/layout.haml +20 -0
  34. data/examples/grape-and-her/consumer/app/views/organizations/index.haml +25 -0
  35. data/examples/grape-and-her/consumer/app/views/organizations/show.haml +11 -0
  36. data/examples/grape-and-her/consumer/app/views/users/index.haml +33 -0
  37. data/examples/grape-and-her/consumer/app/views/users/show.haml +9 -0
  38. data/examples/grape-and-her/consumer/config.ru +20 -0
  39. data/examples/grape-and-her/consumer/config/boot.rb +30 -0
  40. data/examples/grape-and-her/consumer/config/unicorn.rb +7 -0
  41. data/examples/grape-and-her/consumer/lib/response_logger.rb +18 -0
  42. data/her.gemspec +2 -2
  43. data/lib/her/model.rb +22 -26
  44. data/lib/her/model/associations.rb +19 -19
  45. data/lib/her/model/attributes.rb +173 -0
  46. data/lib/her/model/base.rb +17 -0
  47. data/lib/her/model/http.rb +58 -242
  48. data/lib/her/model/introspection.rb +7 -8
  49. data/lib/her/model/nested_attributes.rb +3 -3
  50. data/lib/her/model/orm.rb +15 -205
  51. data/lib/her/model/parse.rb +86 -0
  52. data/lib/her/model/paths.rb +54 -14
  53. data/lib/her/version.rb +1 -1
  54. data/spec/model/attributes_spec.rb +139 -0
  55. data/spec/model/dirty_spec.rb +40 -0
  56. data/spec/model/introspection_spec.rb +5 -5
  57. data/spec/model/orm_spec.rb +14 -128
  58. data/spec/model/paths_spec.rb +26 -0
  59. data/spec/model/validations_spec.rb +17 -0
  60. data/spec/spec_helper.rb +7 -32
  61. data/spec/support/extensions/array.rb +5 -0
  62. data/spec/support/extensions/hash.rb +5 -0
  63. data/spec/support/macros/model_macros.rb +29 -0
  64. metadata +52 -15
  65. data/examples/twitter-oauth/Gemfile +0 -13
  66. data/examples/twitter-oauth/app.rb +0 -50
  67. data/examples/twitter-oauth/config.ru +0 -5
  68. data/examples/twitter-oauth/views/index.haml +0 -9
  69. data/examples/twitter-search/Gemfile +0 -12
  70. data/examples/twitter-search/app.rb +0 -55
  71. data/examples/twitter-search/config.ru +0 -5
  72. 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,13 @@
1
+ class Organization
2
+ include Her::Model
3
+
4
+ # Attributes
5
+ attributes :name
6
+
7
+ # Associations
8
+ has_many :users
9
+
10
+ # Parsing options
11
+ parse_root_in_json true
12
+ include_root_in_json true
13
+ end
@@ -0,0 +1,13 @@
1
+ class User
2
+ include Her::Model
3
+
4
+ # Attributes
5
+ attributes :email, :fullname, :organization_id
6
+
7
+ # Associations
8
+ belongs_to :organization
9
+
10
+ # Parsing options
11
+ parse_root_in_json true
12
+ include_root_in_json true
13
+ end
@@ -0,0 +1,9 @@
1
+ %h2
2
+ %a{ href: '/organizations' } Organizations
3
+ %pre= File.read(File.join(settings.root, "app/models/organization.rb"))
4
+
5
+ %hr
6
+
7
+ %h2
8
+ %a{ href: '/users' } Users
9
+ %pre= File.read(File.join(settings.root, "app/models/user.rb"))
@@ -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,11 @@
1
+ %h1= @organization.name
2
+
3
+ %h2 Users
4
+ %ul.details
5
+ - @organization.users.each do |user|
6
+ %li
7
+ %a{ href: "/users/#{user.id}" }= user.fullname
8
+
9
+ %hr
10
+ .back
11
+ %p Back to <a href="/organizations">organizations</a>
@@ -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,9 @@
1
+ %h1= @user.fullname
2
+
3
+ %ul.details
4
+ %li <strong>Email:</strong> #{@user.email}
5
+ %li <strong>Organization:</strong> <a href="/organizations/#{@user.organization_id}">#{@user.organization.name}</a>
6
+
7
+ %hr
8
+ .back
9
+ %p Back to <a href="/users">users</a>
@@ -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,7 @@
1
+ if ENV["RACK_ENV"] == "development"
2
+ worker_processes 1
3
+ else
4
+ worker_processes 4
5
+ end
6
+
7
+ timeout 30
@@ -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 "mocha", "~> 0.13"
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.5"
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
- # Instance methods
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
- extend Her::Model::Base
37
- extend Her::Model::HTTP
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
- extend ActiveModel::Callbacks
42
- define_model_callbacks :create, :update, :save, :find, :destroy
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
- # Define default settings
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
- # Returns true if attribute_name is
53
- # * in orm data
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
- # Returns
61
- # * the value of the attribute_nane attribute if it's in orm data
62
- # * the resource/collection corrsponding to attribute_name if it's an association
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