alf-rest 0.14.0

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 (44) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/Gemfile +21 -0
  3. data/Gemfile.lock +71 -0
  4. data/LICENCE.md +22 -0
  5. data/Manifest.txt +12 -0
  6. data/README.md +11 -0
  7. data/Rakefile +11 -0
  8. data/lib/alf-rest.rb +1 -0
  9. data/lib/alf/rest.rb +30 -0
  10. data/lib/alf/rest/alf-ext/renderer.rb +16 -0
  11. data/lib/alf/rest/alf-ext/unit_of_work.rb +3 -0
  12. data/lib/alf/rest/alf-ext/unit_of_work/delete.rb +21 -0
  13. data/lib/alf/rest/alf-ext/unit_of_work/insert.rb +22 -0
  14. data/lib/alf/rest/alf-ext/unit_of_work/update.rb +22 -0
  15. data/lib/alf/rest/config.rb +55 -0
  16. data/lib/alf/rest/errors.rb +5 -0
  17. data/lib/alf/rest/helpers.rb +68 -0
  18. data/lib/alf/rest/loader.rb +5 -0
  19. data/lib/alf/rest/middleware.rb +19 -0
  20. data/lib/alf/rest/payload.rb +12 -0
  21. data/lib/alf/rest/payload/client.rb +22 -0
  22. data/lib/alf/rest/request.rb +43 -0
  23. data/lib/alf/rest/response.rb +21 -0
  24. data/lib/alf/rest/test.rb +16 -0
  25. data/lib/alf/rest/test/client.rb +83 -0
  26. data/lib/alf/rest/test/ext.rb +7 -0
  27. data/lib/alf/rest/test/steps.rb +286 -0
  28. data/lib/alf/rest/version.rb +16 -0
  29. data/lib/sinatra/alf-rest.rb +73 -0
  30. data/spec/fixtures/sap.db +0 -0
  31. data/spec/integration/sinatra/rest_get/test_accept.rb +98 -0
  32. data/spec/integration/spec_helper.rb +27 -0
  33. data/spec/test_rest.rb +10 -0
  34. data/spec/unit/config/test_database.rb +28 -0
  35. data/spec/unit/config/test_viewpoint.rb +18 -0
  36. data/spec/unit/ext/renderer/test_from_http_accept.rb +50 -0
  37. data/spec/unit/ext/renderer/test_supported_media_types.rb +10 -0
  38. data/spec/unit/middleware/test_behavior.rb +55 -0
  39. data/spec/unit/request/test_to_relation.rb +56 -0
  40. data/spec/unit/spec_helper.rb +35 -0
  41. data/spec/unit/test_rest.rb +10 -0
  42. data/tasks/gem.rake +8 -0
  43. data/tasks/test.rake +17 -0
  44. metadata +251 -0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # 0.14.0 / 2013-10-01
2
+
3
+ * Enhancements
4
+
5
+ * Birthday!
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source 'http://rubygems.org'
2
+
3
+ group :runtime do
4
+ gem "alf-core", "~> 0.14.0"
5
+ gem "sinatra", "~> 1.3", ">= 1.3.2"
6
+ gem "rack-accept", "~> 0.4.5"
7
+ end
8
+
9
+ group :development do
10
+ gem "rake", "~> 10.0"
11
+ gem "rspec", "~> 2.12"
12
+ end
13
+
14
+ group :test do
15
+ gem "cucumber", "~> 1.2"
16
+ #gem "rack-test", "~> 0.6.1"
17
+ gem "rack-test", :git => "git://github.com/brynary/rack-test.git"
18
+ gem "alf-sequel", "~> 0.14.0"
19
+ gem "sqlite3", "~> 1.3", :platforms => ['mri', 'rbx']
20
+ gem "jdbc-sqlite3", "~> 3.7", :platforms => ['jruby']
21
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,71 @@
1
+ GIT
2
+ remote: git://github.com/brynary/rack-test.git
3
+ revision: 280ff54f50d25dd70e2ec1c55049e5ef7de126f3
4
+ specs:
5
+ rack-test (0.6.2)
6
+ rack (>= 1.0)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ alf-core (0.14.0)
12
+ domain (~> 1.0)
13
+ myrrha (~> 3.0)
14
+ path (~> 1.3)
15
+ sexpr (~> 0.5.1)
16
+ alf-sequel (0.14.0)
17
+ alf-core (~> 0.14.0)
18
+ sequel (~> 3.48)
19
+ builder (3.2.2)
20
+ cucumber (1.3.5)
21
+ builder (>= 2.1.2)
22
+ diff-lcs (>= 1.1.3)
23
+ gherkin (~> 2.12.0)
24
+ multi_json (~> 1.7.5)
25
+ multi_test (>= 0.0.2)
26
+ diff-lcs (1.2.4)
27
+ domain (1.0.0)
28
+ gherkin (2.12.0)
29
+ multi_json (~> 1.3)
30
+ multi_json (1.7.7)
31
+ multi_test (0.0.2)
32
+ myrrha (3.0.0)
33
+ domain (~> 1.0)
34
+ path (1.3.3)
35
+ rack (1.5.2)
36
+ rack-accept (0.4.5)
37
+ rack (>= 0.4)
38
+ rack-protection (1.5.0)
39
+ rack
40
+ rake (10.1.0)
41
+ rspec (2.14.1)
42
+ rspec-core (~> 2.14.0)
43
+ rspec-expectations (~> 2.14.0)
44
+ rspec-mocks (~> 2.14.0)
45
+ rspec-core (2.14.4)
46
+ rspec-expectations (2.14.0)
47
+ diff-lcs (>= 1.1.3, < 2.0)
48
+ rspec-mocks (2.14.1)
49
+ sequel (3.48.0)
50
+ sexpr (0.5.1)
51
+ sinatra (1.4.3)
52
+ rack (~> 1.4)
53
+ rack-protection (~> 1.4)
54
+ tilt (~> 1.3, >= 1.3.4)
55
+ sqlite3 (1.3.7)
56
+ tilt (1.4.1)
57
+
58
+ PLATFORMS
59
+ ruby
60
+
61
+ DEPENDENCIES
62
+ alf-core (~> 0.14.0)
63
+ alf-sequel (~> 0.14.0)
64
+ cucumber (~> 1.2)
65
+ jdbc-sqlite3 (~> 3.7)
66
+ rack-accept (~> 0.4.5)
67
+ rack-test!
68
+ rake (~> 10.0)
69
+ rspec (~> 2.12)
70
+ sinatra (~> 1.3, >= 1.3.2)
71
+ sqlite3 (~> 1.3)
data/LICENCE.md ADDED
@@ -0,0 +1,22 @@
1
+ # The MIT Licence
2
+
3
+ Copyright (c) 2012 - Bernard Lambeau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,12 @@
1
+ rest.gemspec
2
+ rest.noespec
3
+ CHANGELOG.md
4
+ Gemfile
5
+ Gemfile.lock
6
+ lib/**/*
7
+ LICENCE.md
8
+ Manifest.txt
9
+ Rakefile
10
+ README.md
11
+ spec/**/*
12
+ tasks/**/*
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Alf::Rest
2
+
3
+ [![Build Status](https://secure.travis-ci.org/alf-tool/alf-rest.png)](http://travis-ci.org/alf-tool/alf-rest)
4
+ [![Dependency Status](https://gemnasium.com/alf-tool/alf-rest.png)](https://gemnasium.com/alf-tool/alf-rest)
5
+
6
+ Put your alf relational database on the web quickly, simply, safely.
7
+
8
+ ## Links
9
+
10
+ http://github.com/alf-tool/alf
11
+ http://github.com/alf-tool/alf-rest
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # We run tests by default
2
+ task :default => :test
3
+
4
+ #
5
+ # Install all tasks found in tasks folder
6
+ #
7
+ # See .rake files there for complete documentation.
8
+ #
9
+ Dir["tasks/*.rake"].each do |taskfile|
10
+ load taskfile
11
+ end
data/lib/alf-rest.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative "alf/rest"
data/lib/alf/rest.rb ADDED
@@ -0,0 +1,30 @@
1
+ require_relative 'rest/version'
2
+ require_relative 'rest/loader'
3
+ require_relative 'rest/errors'
4
+ require_relative 'rest/alf-ext/renderer'
5
+ require_relative 'rest/alf-ext/unit_of_work'
6
+ module Alf
7
+ module Rest
8
+
9
+ # there are circular dependencies due to config default values :-(
10
+ class Agent; end
11
+ class ErrorApp < Sinatra::Base; end
12
+
13
+ RACK_CONFIG_KEY = 'alf-rest-config'
14
+
15
+ RACK_ERROR_KEY = 'alf-rest-error'
16
+
17
+ def self.new(app, config = Config.new)
18
+ yield(config) if block_given?
19
+ Middleware.new(app, config)
20
+ end
21
+
22
+ end # module Rest
23
+ end # module Alf
24
+ require_relative 'rest/payload'
25
+ require_relative 'rest/request'
26
+ require_relative 'rest/response'
27
+ require_relative 'rest/helpers'
28
+
29
+ require_relative 'rest/config'
30
+ require_relative 'rest/middleware'
@@ -0,0 +1,16 @@
1
+ module Alf
2
+ class Renderer
3
+
4
+ def self.supported_media_types
5
+ each.map{|(_,_,r)| r.mime_type}.compact.sort
6
+ end
7
+
8
+ def self.from_http_accept(accept)
9
+ media_type = Rack::Accept::MediaType.new(accept)
10
+ if best = media_type.best_of(supported_media_types)
11
+ each.find{|(name,_,r)| r.mime_type == best }.last
12
+ end
13
+ end
14
+
15
+ end # class Renderer
16
+ end # module Alf
@@ -0,0 +1,3 @@
1
+ require_relative 'unit_of_work/insert'
2
+ require_relative 'unit_of_work/update'
3
+ require_relative 'unit_of_work/delete'
@@ -0,0 +1,21 @@
1
+ module Alf
2
+ module Sequel
3
+ module UnitOfWork
4
+ class Delete
5
+
6
+ def rack_status
7
+ 200
8
+ end
9
+
10
+ def rack_body
11
+ {status: "success", message: "deleted"}
12
+ end
13
+
14
+ def rack_location(request)
15
+ nil
16
+ end
17
+
18
+ end # class Delete
19
+ end # module UnitOfWork
20
+ end # module Sequel
21
+ end # module Alf
@@ -0,0 +1,22 @@
1
+ module Alf
2
+ module Sequel
3
+ module UnitOfWork
4
+ class Insert
5
+
6
+ def rack_status
7
+ 201
8
+ end
9
+
10
+ def rack_body
11
+ {status: "success", message: "created"}
12
+ end
13
+
14
+ def rack_location(request)
15
+ ids = matching_relation.tuple_extract.to_hash.values
16
+ "#{request.path}/#{ids.join(',')}"
17
+ end
18
+
19
+ end # class Insert
20
+ end # module UnitOfWork
21
+ end # module Sequel
22
+ end # module Alf
@@ -0,0 +1,22 @@
1
+ module Alf
2
+ module Sequel
3
+ module UnitOfWork
4
+ class Update
5
+
6
+ def rack_status
7
+ 200
8
+ end
9
+
10
+ def rack_body
11
+ {status: "success", message: "updated"}
12
+ end
13
+
14
+ def rack_location(request)
15
+ ids = pk_matching_relation.tuple_extract.to_hash.values
16
+ "#{request.path}/#{ids.join(',')}"
17
+ end
18
+
19
+ end # class Insert
20
+ end # module UnitOfWork
21
+ end # module Sequel
22
+ end # module Alf
@@ -0,0 +1,55 @@
1
+ module Alf
2
+ module Rest
3
+ class Config < Support::Config
4
+
5
+ # The database instance to use for obtaining connections
6
+ option :database, Database, nil
7
+
8
+ # The connection options to use
9
+ option :connection_options, Hash, {}
10
+
11
+ # Enclose all requests in a single database transaction
12
+ option :transactional, Boolean, true
13
+
14
+ # The logger instance to use for logging
15
+ option :logger, Object, Logger.new(STDOUT)
16
+
17
+ # The current database connection
18
+ attr_reader :connection
19
+
20
+ # Yields the block with the database connection
21
+ def connect(&bl)
22
+ return yield unless database
23
+ database.connect(connection_options) do |conn|
24
+ @connection = conn
25
+ if transactional?
26
+ conn.in_transaction{ yield(conn) }
27
+ else
28
+ yield(conn)
29
+ end
30
+ end
31
+ end
32
+
33
+ # Reconnect
34
+ def reconnect(opts)
35
+ connection.reconnect(opts)
36
+ end
37
+
38
+ # Sets the database, coercing it if required
39
+ def database=(db)
40
+ @database = db.is_a?(Database) ? db : Alf.database(db)
41
+ end
42
+
43
+ # Returns the default viewpoint to use
44
+ def viewpoint
45
+ connection_options[:viewpoint]
46
+ end
47
+
48
+ # Sets the default viewpoint on connection options
49
+ def viewpoint=(vp)
50
+ connection_options[:viewpoint] = vp
51
+ end
52
+
53
+ end # class Config
54
+ end # module Rest
55
+ end # module Alf
@@ -0,0 +1,5 @@
1
+ module Alf
2
+ class FactAssertionError
3
+ attr_accessor :http_error_status
4
+ end
5
+ end
@@ -0,0 +1,68 @@
1
+ module Alf
2
+ module Rest
3
+ module Helpers
4
+ extend Forwardable
5
+
6
+ def alf_config
7
+ env[Rest::RACK_CONFIG_KEY]
8
+ end
9
+
10
+ def db_conn
11
+ alf_config.connection
12
+ end
13
+
14
+ def with_db_conn
15
+ yield(db_conn)
16
+ end
17
+
18
+ def_delegators :db_conn, :relvar,
19
+ :query,
20
+ :tuple_extract
21
+
22
+ def to_location(url, ids)
23
+ ids = ids.matching_relation if ids.respond_to?(:matching_relation)
24
+ ids = ids.tuple_extract if ids.respond_to?(:tuple_extract)
25
+ ids = ids.to_hash.values
26
+ "#{url}/#{ids.join(',')}"
27
+ end
28
+
29
+ def location_set?
30
+ response.headers["Location"]
31
+ end
32
+
33
+ def assert!(msg='an assertion failed', status=nil, &bl)
34
+ db_conn.assert!(msg, &bl)
35
+ rescue FactAssertionError => ex
36
+ ex.http_error_status = status
37
+ raise
38
+ end
39
+
40
+ def deny!(msg='an assertion failed', status=nil, &bl)
41
+ db_conn.deny!(msg, &bl)
42
+ rescue FactAssertionError => ex
43
+ ex.http_error_status = status
44
+ raise
45
+ end
46
+
47
+ def fact!(msg='an assertion failed', status=nil, &bl)
48
+ db_conn.fact!(msg, &bl)
49
+ rescue FactAssertionError => ex
50
+ ex.http_error_status = status
51
+ raise
52
+ end
53
+
54
+ def no_duplicate!(&bl)
55
+ found = relvar(&bl)
56
+ unless found.empty?
57
+ ids = found.project(found.keys.first.to_attr_list)
58
+ halt Alf::Rest::Response.new(env){|r|
59
+ r.status = 200
60
+ r.body = {'status' => 'success', 'message' => 'skipped'}
61
+ r["Location"] = to_location(request.path, ids)
62
+ }.finish
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ require 'json'
2
+ require 'logger'
3
+ require "alf-core"
4
+ require "sinatra"
5
+ require "rack/accept"
@@ -0,0 +1,19 @@
1
+ module Alf
2
+ module Rest
3
+ class Middleware
4
+
5
+ def initialize(app, config)
6
+ @app = app
7
+ @config = config
8
+ end
9
+
10
+ def call(env)
11
+ env[Rest::RACK_CONFIG_KEY] = cfg = @config.dup
12
+ cfg.connect do
13
+ @app.call(env)
14
+ end
15
+ end
16
+
17
+ end # class Middleware
18
+ end # module Rest
19
+ end # module Alf