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.
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