dao 0.0.0 → 2.0.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 (90) hide show
  1. data/README +35 -0
  2. data/Rakefile +6 -3
  3. data/TODO +37 -0
  4. data/dao.gemspec +30 -0
  5. data/db/dao.yml +8 -0
  6. data/lib/dao.rb +84 -5
  7. data/lib/dao/active_record.rb +76 -0
  8. data/lib/dao/api.rb +9 -0
  9. data/lib/dao/api/context.rb +38 -0
  10. data/lib/dao/api/dsl.rb +50 -0
  11. data/lib/dao/api/endpoints.rb +190 -0
  12. data/lib/dao/api/initializers.rb +71 -0
  13. data/lib/dao/api/modes.rb +85 -0
  14. data/lib/dao/blankslate.rb +5 -0
  15. data/lib/dao/data.rb +58 -0
  16. data/lib/dao/db.rb +183 -0
  17. data/lib/dao/endpoint.rb +16 -0
  18. data/lib/dao/engine.rb +7 -0
  19. data/lib/dao/errors.rb +238 -0
  20. data/lib/dao/exceptions.rb +2 -0
  21. data/lib/dao/form.rb +236 -0
  22. data/lib/dao/mode.rb +41 -0
  23. data/lib/dao/mongo_mapper.rb +70 -0
  24. data/lib/dao/params.rb +109 -0
  25. data/lib/dao/path.rb +149 -0
  26. data/lib/dao/rails.rb +15 -0
  27. data/lib/dao/rails/app/api.rb +55 -0
  28. data/lib/dao/rails/app/controllers/api_controller.rb +99 -0
  29. data/lib/dao/rails/lib/generators/dao/USAGE +9 -0
  30. data/lib/dao/rails/lib/generators/dao/api_generator.rb +3 -0
  31. data/lib/dao/rails/lib/generators/dao/dao_generator.rb +27 -0
  32. data/lib/dao/rails/lib/generators/dao/templates/api.rb +55 -0
  33. data/lib/dao/rails/lib/generators/dao/templates/api_controller.rb +99 -0
  34. data/lib/dao/result.rb +87 -0
  35. data/lib/dao/slug.rb +11 -0
  36. data/lib/dao/status.rb +223 -0
  37. data/lib/dao/stdext.rb +10 -0
  38. data/lib/dao/support.rb +62 -0
  39. data/lib/dao/validations.rb +115 -0
  40. data/sample/rails_app/Gemfile +33 -0
  41. data/sample/rails_app/Gemfile.lock +88 -0
  42. data/sample/rails_app/README +1 -0
  43. data/sample/rails_app/Rakefile +7 -0
  44. data/sample/rails_app/app/api.rb +55 -0
  45. data/sample/rails_app/app/controllers/api_controller.rb +99 -0
  46. data/sample/rails_app/app/controllers/application_controller.rb +3 -0
  47. data/sample/rails_app/app/helpers/application_helper.rb +2 -0
  48. data/sample/rails_app/app/views/layouts/application.html.erb +14 -0
  49. data/sample/rails_app/config.ru +4 -0
  50. data/sample/rails_app/config/application.rb +51 -0
  51. data/sample/rails_app/config/boot.rb +13 -0
  52. data/sample/rails_app/config/database.yml +22 -0
  53. data/sample/rails_app/config/environment.rb +5 -0
  54. data/sample/rails_app/config/environments/development.rb +26 -0
  55. data/sample/rails_app/config/environments/production.rb +49 -0
  56. data/sample/rails_app/config/environments/test.rb +35 -0
  57. data/sample/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  58. data/sample/rails_app/config/initializers/inflections.rb +10 -0
  59. data/sample/rails_app/config/initializers/mime_types.rb +5 -0
  60. data/sample/rails_app/config/initializers/secret_token.rb +7 -0
  61. data/sample/rails_app/config/initializers/session_store.rb +8 -0
  62. data/sample/rails_app/config/locales/en.yml +5 -0
  63. data/sample/rails_app/config/routes.rb +62 -0
  64. data/sample/rails_app/db/development.sqlite3 +0 -0
  65. data/sample/rails_app/db/seeds.rb +7 -0
  66. data/sample/rails_app/doc/README_FOR_APP +2 -0
  67. data/sample/rails_app/log/development.log +27 -0
  68. data/sample/rails_app/log/production.log +0 -0
  69. data/sample/rails_app/log/server.log +0 -0
  70. data/sample/rails_app/log/test.log +0 -0
  71. data/sample/rails_app/public/404.html +26 -0
  72. data/sample/rails_app/public/422.html +26 -0
  73. data/sample/rails_app/public/500.html +26 -0
  74. data/sample/rails_app/public/favicon.ico +0 -0
  75. data/sample/rails_app/public/images/rails.png +0 -0
  76. data/sample/rails_app/public/index.html +239 -0
  77. data/sample/rails_app/public/javascripts/application.js +2 -0
  78. data/sample/rails_app/public/javascripts/controls.js +965 -0
  79. data/sample/rails_app/public/javascripts/dragdrop.js +974 -0
  80. data/sample/rails_app/public/javascripts/effects.js +1123 -0
  81. data/sample/rails_app/public/javascripts/prototype.js +6001 -0
  82. data/sample/rails_app/public/javascripts/rails.js +175 -0
  83. data/sample/rails_app/public/robots.txt +5 -0
  84. data/sample/rails_app/script/rails +6 -0
  85. data/sample/rails_app/test/performance/browsing_test.rb +9 -0
  86. data/sample/rails_app/test/test_helper.rb +13 -0
  87. data/test/dao_test.rb +271 -0
  88. data/test/helper.rb +15 -0
  89. data/test/testing.rb +74 -0
  90. metadata +137 -9
data/README ADDED
@@ -0,0 +1,35 @@
1
+ NAME
2
+ dao
3
+
4
+ SYNOPSIS
5
+ a library for structuring rails applications using the 'data access object' pattern
6
+
7
+ DESCRITPION
8
+ stay tuned...
9
+
10
+ ISSUES ADRESSED
11
+ . testability
12
+ . documentability
13
+ . being able to reason about code
14
+ . reasoning about caching
15
+ . avoiding duplicating logic in html/api
16
+ . avoid N+1 in view
17
+ . avoid design issues with REST (delete(1,2,3))
18
+ . nested attributes trivally
19
+ . lack of domain (current_user)
20
+ . easily optimize queries later
21
+ . magic
22
+
23
+ READING
24
+ http://www.paperplanes.de/2010/5/7/activerecord_callbacks_ruined_my_life.html
25
+ http://best-practice-software-engineering.ifs.tuwien.ac.at/patterns/dao.html
26
+ http://www.codefutures.com/data-access-object/
27
+ http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html
28
+
29
+ INSTALL
30
+ gem 'dao', :path => File.expand_path('..') ### Gemfile
31
+ rails generate dao api
32
+ vim -o app/api.rb app/controllers/api_controller.rb
33
+ curl --silent http://0.0.0.0:3000/api
34
+ curl --silent http://0.0.0.0:3000/api/ping
35
+
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  This.rubyforge_project = 'codeforpeople'
2
2
  This.author = "Ara T. Howard"
3
3
  This.email = "ara.t.howard@gmail.com"
4
- This.homepage = "http://github.com/ahoward/#{ This.lib }/tree/master"
4
+ This.homepage = "http://github.com/ahoward/#{ This.lib }"
5
5
 
6
6
 
7
7
  task :default do
@@ -26,7 +26,7 @@ def run_tests!(which = nil)
26
26
 
27
27
  div = ('=' * 119)
28
28
  line = ('-' * 119)
29
- helper = "-r ./test/helper.rb" if test(?e, "./test/helper.rb")
29
+ helper = nil #"-r ./test/helper.rb" if test(?e, "./test/helper.rb")
30
30
 
31
31
  test_rbs.each_with_index do |test_rb, index|
32
32
  testno = index + 1
@@ -126,6 +126,9 @@ task :gemspec do
126
126
  spec.test_files = #{ test_files.inspect }
127
127
 
128
128
  # spec.add_dependency 'lib', '>= version'
129
+ spec.add_dependency 'map'
130
+ spec.add_dependency 'tagz'
131
+ spec.add_dependency 'yajl-ruby'
129
132
 
130
133
  spec.extensions.push(*#{ extensions.inspect })
131
134
 
@@ -139,7 +142,7 @@ task :gemspec do
139
142
  end
140
143
 
141
144
  Fu.mkdir_p(This.pkgdir)
142
- This.gemspec = File.join(This.pkgdir, "gemspec.rb")
145
+ This.gemspec = File.join(This.dir, "#{ This.lib }.gemspec") #File.join(This.pkgdir, "gemspec.rb")
143
146
  open("#{ This.gemspec }", "w"){|fd| fd.puts(template)}
144
147
  end
145
148
 
data/TODO ADDED
@@ -0,0 +1,37 @@
1
+ todo:
2
+
3
+ route -> path??
4
+
5
+ - nested AR conversions
6
+ to_dao(:a, :b => [:foo, :bar])
7
+
8
+ - AR as_dao contains the model type/class.name
9
+
10
+
11
+ done:
12
+ - engine-ify the rails stuff? or what
13
+ - generator api
14
+ - controller
15
+ - helper
16
+ - json/pretty fix baked in? (yajl might simply this...)
17
+ - description/doc logic
18
+ - api.index
19
+ - to_alpo -> to_dao
20
+ - tests!
21
+ - #call auto parses data iff appropriate
22
+ @result = api.read.call('/posts/new(', params) ### check keys for '/posts/new'
23
+ - re-visit how parameters are parsed, perhaps we just use rack?
24
+ - next 'data' => {}
25
+ - check the db layer
26
+ - make sure one can call route based method (/foo/:bar) methods with params...
27
+ - Path==Route ??
28
+ - add result.rb, params.rb
29
+ - name -> path
30
+ - tagz for html methods?
31
+ - data aquires the name/path of the method?
32
+ - endpoints are objects that respond to call. namespaces respond to call.
33
+ api responds to call
34
+ - nested namespaces
35
+ - routing for path_info + mode
36
+ route(path_info, :mode => mode, :params => params)
37
+ - call() on namespaces AND endpoints... why?
data/dao.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ ## dao.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "dao"
6
+ spec.version = "2.0.0"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "dao"
9
+ spec.description = "description: dao kicks the ass"
10
+
11
+ spec.files = ["dao.gemspec", "db", "db/dao.yml", "lib", "lib/dao", "lib/dao/active_record.rb", "lib/dao/api", "lib/dao/api/context.rb", "lib/dao/api/dsl.rb", "lib/dao/api/endpoints.rb", "lib/dao/api/initializers.rb", "lib/dao/api/modes.rb", "lib/dao/api.rb", "lib/dao/blankslate.rb", "lib/dao/data.rb", "lib/dao/db.rb", "lib/dao/endpoint.rb", "lib/dao/engine.rb", "lib/dao/errors.rb", "lib/dao/exceptions.rb", "lib/dao/form.rb", "lib/dao/mode.rb", "lib/dao/mongo_mapper.rb", "lib/dao/params.rb", "lib/dao/path.rb", "lib/dao/rails", "lib/dao/rails/app", "lib/dao/rails/app/api.rb", "lib/dao/rails/app/controllers", "lib/dao/rails/app/controllers/api_controller.rb", "lib/dao/rails/engine", "lib/dao/rails/lib", "lib/dao/rails/lib/generators", "lib/dao/rails/lib/generators/dao", "lib/dao/rails/lib/generators/dao/api_generator.rb", "lib/dao/rails/lib/generators/dao/dao_generator.rb", "lib/dao/rails/lib/generators/dao/templates", "lib/dao/rails/lib/generators/dao/templates/api.rb", "lib/dao/rails/lib/generators/dao/templates/api_controller.rb", "lib/dao/rails/lib/generators/dao/USAGE", "lib/dao/rails.rb", "lib/dao/result.rb", "lib/dao/slug.rb", "lib/dao/status.rb", "lib/dao/stdext.rb", "lib/dao/support.rb", "lib/dao/validations.rb", "lib/dao.rb", "Rakefile", "README", "sample", "sample/rails_app", "sample/rails_app/app", "sample/rails_app/app/api.rb", "sample/rails_app/app/controllers", "sample/rails_app/app/controllers/api_controller.rb", "sample/rails_app/app/controllers/application_controller.rb", "sample/rails_app/app/helpers", "sample/rails_app/app/helpers/application_helper.rb", "sample/rails_app/app/mailers", "sample/rails_app/app/models", "sample/rails_app/app/views", "sample/rails_app/app/views/layouts", "sample/rails_app/app/views/layouts/application.html.erb", "sample/rails_app/config", "sample/rails_app/config/application.rb", "sample/rails_app/config/boot.rb", "sample/rails_app/config/database.yml", "sample/rails_app/config/environment.rb", "sample/rails_app/config/environments", "sample/rails_app/config/environments/development.rb", "sample/rails_app/config/environments/production.rb", "sample/rails_app/config/environments/test.rb", "sample/rails_app/config/initializers", "sample/rails_app/config/initializers/backtrace_silencers.rb", "sample/rails_app/config/initializers/inflections.rb", "sample/rails_app/config/initializers/mime_types.rb", "sample/rails_app/config/initializers/secret_token.rb", "sample/rails_app/config/initializers/session_store.rb", "sample/rails_app/config/locales", "sample/rails_app/config/locales/en.yml", "sample/rails_app/config/routes.rb", "sample/rails_app/config.ru", "sample/rails_app/db", "sample/rails_app/db/development.sqlite3", "sample/rails_app/db/seeds.rb", "sample/rails_app/doc", "sample/rails_app/doc/README_FOR_APP", "sample/rails_app/Gemfile", "sample/rails_app/Gemfile.lock", "sample/rails_app/lib", "sample/rails_app/lib/tasks", "sample/rails_app/log", "sample/rails_app/log/development.log", "sample/rails_app/log/production.log", "sample/rails_app/log/server.log", "sample/rails_app/log/test.log", "sample/rails_app/public", "sample/rails_app/public/404.html", "sample/rails_app/public/422.html", "sample/rails_app/public/500.html", "sample/rails_app/public/favicon.ico", "sample/rails_app/public/images", "sample/rails_app/public/images/rails.png", "sample/rails_app/public/index.html", "sample/rails_app/public/javascripts", "sample/rails_app/public/javascripts/application.js", "sample/rails_app/public/javascripts/controls.js", "sample/rails_app/public/javascripts/dragdrop.js", "sample/rails_app/public/javascripts/effects.js", "sample/rails_app/public/javascripts/prototype.js", "sample/rails_app/public/javascripts/rails.js", "sample/rails_app/public/robots.txt", "sample/rails_app/public/stylesheets", "sample/rails_app/Rakefile", "sample/rails_app/README", "sample/rails_app/script", "sample/rails_app/script/rails", "sample/rails_app/test", "sample/rails_app/test/fixtures", "sample/rails_app/test/functional", "sample/rails_app/test/integration", "sample/rails_app/test/performance", "sample/rails_app/test/performance/browsing_test.rb", "sample/rails_app/test/test_helper.rb", "sample/rails_app/test/unit", "sample/rails_app/tmp/cache", "sample/rails_app/tmp/pids", "sample/rails_app/tmp/sessions", "sample/rails_app/tmp/sockets", "sample/rails_app/vendor", "sample/rails_app/vendor/plugins", "test", "test/dao_test.rb", "test/helper.rb", "test/testing.rb", "test/units", "TODO"]
12
+ spec.executables = []
13
+
14
+ spec.require_path = "lib"
15
+
16
+ spec.has_rdoc = true
17
+ spec.test_files = nil
18
+
19
+ # spec.add_dependency 'lib', '>= version'
20
+ spec.add_dependency 'map'
21
+ spec.add_dependency 'tagz'
22
+ spec.add_dependency 'yajl-ruby'
23
+
24
+ spec.extensions.push(*[])
25
+
26
+ spec.rubyforge_project = "codeforpeople"
27
+ spec.author = "Ara T. Howard"
28
+ spec.email = "ara.t.howard@gmail.com"
29
+ spec.homepage = "http://github.com/ahoward/dao"
30
+ end
data/db/dao.yml ADDED
@@ -0,0 +1,8 @@
1
+ ---
2
+ "42": {}
3
+
4
+ users:
5
+ "42":
6
+ a: :b
7
+ time: 2011-01-26 13:27:36.996654 -07:00
8
+ id: 42
data/lib/dao.rb CHANGED
@@ -1,9 +1,88 @@
1
- module Dao
1
+ # built-ins
2
+ #
3
+ require 'enumerator'
4
+ #require 'fileutils'
5
+ #require 'pathname'
6
+ #require 'yaml'
7
+ #require 'yaml/store'
2
8
 
3
- def Dao.version
4
- '0.0.0'
9
+ # gems
10
+ #
11
+ begin
12
+ require 'rubygems'
13
+ rescue LoadError
14
+ nil
5
15
  end
6
16
 
7
- end
17
+ if defined?(gem)
18
+ gem('map', '~> 2.2.2')
19
+ gem('tagz', '~> 8.0')
20
+ gem('yajl-ruby', '~> 0.7.9')
21
+ end
22
+
23
+ require 'map'
24
+ require 'tagz'
25
+ require 'yajl'
26
+
27
+ # dao libs
28
+ #
29
+ module Dao
30
+ Version = '2.0.0' unless defined?(Version)
31
+
32
+ def version
33
+ Dao::Version
34
+ end
35
+
36
+ def libdir(*args, &block)
37
+ @libdir ||= File.expand_path(__FILE__).sub(/\.rb$/,'')
38
+ args.empty? ? @libdir : File.join(@libdir, *args)
39
+ ensure
40
+ if block
41
+ begin
42
+ $LOAD_PATH.unshift(@libdir)
43
+ block.call()
44
+ ensure
45
+ $LOAD_PATH.shift()
46
+ end
47
+ end
48
+ end
49
+
50
+ def load(*libs)
51
+ libs = libs.join(' ').scan(/[^\s+]+/)
52
+ Dao.libdir{ libs.each{|lib| Kernel.load(lib) } }
53
+ end
54
+
55
+ extend(Dao)
56
+ end
8
57
 
9
- DAO = Dao
58
+ Dao.load %w[
59
+ blankslate.rb
60
+ exceptions.rb
61
+ support.rb
62
+ slug.rb
63
+ stdext.rb
64
+
65
+ result.rb
66
+ params.rb
67
+ status.rb
68
+ data.rb
69
+ form.rb
70
+ errors.rb
71
+ validations.rb
72
+
73
+ mode.rb
74
+ path.rb
75
+ endpoint.rb
76
+ api.rb
77
+
78
+
79
+ rails.rb
80
+ active_record.rb
81
+ mongo_mapper.rb
82
+ ]
83
+
84
+ Dao.autoload(:Db, Dao.libdir('db.rb'))
85
+
86
+ unless defined?(D)
87
+ D = Dao
88
+ end
@@ -0,0 +1,76 @@
1
+ begin
2
+ ActiveRecord
3
+ ActiveRecord::Base
4
+ rescue NameError
5
+ nil
6
+ end
7
+
8
+ if defined?(ActiveRecord)
9
+
10
+ module ActiveRecord
11
+ module ToDao
12
+ module ClassMethods
13
+ def to_dao(*args)
14
+
15
+ @to_dao ||= (
16
+ column_names # + reflect_on_all_associations.map(&:name)
17
+ ).map{|name| name.to_s}
18
+
19
+ unless args.empty?
20
+ @to_dao.clear
21
+ args.flatten.compact.each do |arg|
22
+ @to_dao.push(arg.to_s)
23
+ end
24
+ @to_dao.uniq!
25
+ @to_dao.map!{|name| name.to_s}
26
+ end
27
+
28
+ @to_dao
29
+ end
30
+
31
+ def to_dao=(*args)
32
+ to_dao(*args)
33
+ end
34
+ end
35
+
36
+ module InstanceMethods
37
+ def to_dao(*args)
38
+ hash = Dao.hash
39
+ model = self.class
40
+
41
+ attrs = args.empty? ? model.to_dao : args
42
+
43
+ attrs.each do |attr|
44
+ value = send(attr)
45
+
46
+ if value.respond_to?(:to_dao)
47
+ hash[attr] = value.to_dao
48
+ next
49
+ end
50
+
51
+ if value.is_a?(Array)
52
+ hash[attr] = value.map{|val| val.respond_to?(:to_dao) ? val.to_dao : val}
53
+ next
54
+ end
55
+
56
+ hash[attr] = value
57
+ end
58
+
59
+ if hash.has_key?(:_id) and not hash.has_key?(:id)
60
+ hash[:id] = hash[:_id]
61
+ end
62
+
63
+ hash
64
+ end
65
+ alias_method 'to_h', 'to_dao'
66
+ #alias_method 'to_map', 'to_dao' ### HACK
67
+ end
68
+ end
69
+
70
+ if defined?(ActiveRecord::Base)
71
+ ActiveRecord::Base.send(:extend, ToDao::ClassMethods)
72
+ ActiveRecord::Base.send(:include, ToDao::InstanceMethods)
73
+ end
74
+ end
75
+
76
+ end
data/lib/dao/api.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Dao
2
+ class Api
3
+ Dao.load 'api/initializers.rb'
4
+ Dao.load 'api/modes.rb'
5
+ Dao.load 'api/context.rb'
6
+ Dao.load 'api/endpoints.rb'
7
+ Dao.load 'api/dsl.rb'
8
+ end
9
+ end
@@ -0,0 +1,38 @@
1
+ module Dao
2
+ class Context
3
+ Attrs = %w( api endpoint params result method args )
4
+ Attrs.each{|attr| attr_accessor(attr)}
5
+
6
+ def initialize(*args, &block)
7
+ options = Dao.options_for!(args)
8
+
9
+ api = options[:api]
10
+ endpoint = options[:endpoint]
11
+ params = options[:params]
12
+
13
+ params = Params.for(:api => api, :endpoint => endpoint, :params => params)
14
+ result = Result.new(:api => api, :endpoint => endpoint, :params => params)
15
+ params.result = result
16
+
17
+ method = endpoint.method.bind(api)
18
+ args = [params, result].slice(0, method.arity)
19
+
20
+ self.api = api
21
+ self.endpoint = endpoint
22
+ self.params = params
23
+ self.result = result
24
+ self.method = method
25
+ self.args = args
26
+ end
27
+
28
+ def call()
29
+ method.call(*args)
30
+ end
31
+
32
+ def update(options = {})
33
+ options.each do |key, val|
34
+ send("#{ key }=", val)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,50 @@
1
+ module Dao
2
+ class Api
3
+ class DSL < BlankSlate
4
+ attr_accessor :api
5
+
6
+ def initialize(api)
7
+ @api = api
8
+ #@evaluate = Object.instance_method(:instance_eval).bind(self)
9
+ end
10
+
11
+ def evaluate(&block)
12
+ #@evaluate.call(&block)
13
+ @api.module_eval(&block)
14
+ ensure
15
+ #no_docs_left_on_stack!
16
+ end
17
+
18
+ def no_docs_left_on_stack!
19
+ raise "no endpoint for #{ docs.inspect }" unless docs.empty?
20
+ end
21
+
22
+ %w( endpoint doc docs description desc ).each do |method|
23
+ module_eval <<-__, __FILE__, __LINE__ - 1
24
+
25
+ def #{ method }(*args, &block)
26
+ api.#{ method }(*args, &block)
27
+ end
28
+
29
+ __
30
+ end
31
+ end
32
+
33
+ class << Api
34
+ def evaluate(&block)
35
+ @dsl ||= DSL.new(api=self)
36
+ @dsl.evaluate(&block)
37
+ end
38
+ end
39
+ end
40
+
41
+ def Dao.api(&block)
42
+ if block
43
+ api = Class.new(Api)
44
+ api.evaluate(&block)
45
+ api
46
+ else
47
+ Api
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,190 @@
1
+ module Dao
2
+ class Api
3
+ class << Api
4
+ def endpoints
5
+ @endpoints ||= Map.new
6
+ end
7
+
8
+ def endpoint(path, &block)
9
+ api = self
10
+ path = Path.new(path)
11
+
12
+ method =
13
+ module_eval{
14
+ define_method(path + '/endpoint', &block)
15
+ instance_method(path + '/endpoint')
16
+ }
17
+
18
+
19
+ endpoint = Endpoint.new(
20
+ 'api' => api,
21
+ 'path' => path,
22
+ 'method' => method,
23
+ 'doc' => docs.pop
24
+ )
25
+
26
+ endpoints[path] = endpoint
27
+ end
28
+
29
+ def description(string)
30
+ doc(:description => Dao.unindent(string))
31
+ end
32
+ alias_method('desc', 'description')
33
+
34
+ def doc(*args)
35
+ docs.push(Map[:description, nil]) if docs.empty?
36
+ doc = docs.last
37
+ options = Dao.options_for!(args)
38
+ if options.empty?
39
+ options[:description] = args.join(' ')
40
+ end
41
+ doc.update(options)
42
+ doc
43
+ end
44
+
45
+ def docs
46
+ @docs ||= []
47
+ end
48
+
49
+ def index
50
+ index = Map.new
51
+ endpoints.each do |path, endpoint|
52
+ index[path] = endpoint.doc || {'description' => path}
53
+ end
54
+ index
55
+ end
56
+ end
57
+
58
+ def call(path = '/index', params = {})
59
+ api = self
60
+ path = Path.new(path)
61
+ endpoint = endpoints[path]
62
+ raise(NameError, path) unless endpoint
63
+
64
+ params = parse_params(params, path)
65
+
66
+ context = Context.new(
67
+ :api => api,
68
+ :endpoint => endpoint,
69
+ :params => params
70
+ )
71
+
72
+ callstack(context) do
73
+ catching(:result){ context.call() }
74
+ end
75
+
76
+ context.result
77
+ end
78
+
79
+ def index
80
+ self.class.index
81
+ end
82
+
83
+ def parse_params(params, path)
84
+ return params if params.is_a?(Params)
85
+ re = %r/^#{ Regexp.escape(path) }/
86
+ params.each do |key, val|
87
+ return Params.parse(path, params) if key =~ re
88
+ end
89
+ return params
90
+ end
91
+
92
+ def endpoints
93
+ self.class.endpoints
94
+ end
95
+
96
+ def context
97
+ callstack.last
98
+ end
99
+
100
+ def result
101
+ context.result
102
+ end
103
+
104
+ def status(*args, &block)
105
+ result.status(*args, &block)
106
+ end
107
+
108
+ def data
109
+ result.data
110
+ end
111
+
112
+ def errors
113
+ result.errors
114
+ end
115
+
116
+ def params
117
+ result.params
118
+ end
119
+
120
+ def validations
121
+ result.validations
122
+ end
123
+
124
+ def validates(*args, &block)
125
+ result.validates(*args, &block)
126
+ end
127
+
128
+ def validate
129
+ result.validate
130
+ end
131
+
132
+ def valid?
133
+ result.valid?
134
+ end
135
+
136
+ def validate!
137
+ result.validate!
138
+ end
139
+
140
+ def valid!
141
+ result.valid!
142
+ end
143
+
144
+ def callstack(context = nil, &block)
145
+ @callstack ||= []
146
+
147
+ if block and context
148
+ begin
149
+ @callstack.push(context)
150
+ return block.call()
151
+ ensure
152
+ @callstack.pop
153
+ end
154
+ else
155
+ @callstack
156
+ end
157
+ end
158
+
159
+ def catching(label = :result, &block)
160
+ @catching ||= []
161
+
162
+ if block
163
+ begin
164
+ @catching.push(label)
165
+ catch(label, &block)
166
+ ensure
167
+ @catching.pop
168
+ end
169
+ else
170
+ @catching.last
171
+ end
172
+ end
173
+
174
+ def catching_results(&block)
175
+ catching(:result, &block)
176
+ end
177
+
178
+ def catching?
179
+ catching
180
+ end
181
+
182
+ def catching_results?
183
+ catching == :result
184
+ end
185
+
186
+ def respond_to?(*args)
187
+ super(*args) || super(Path.absolute_path_for(*args))
188
+ end
189
+ end
190
+ end