ki 0.3.3 → 0.4.1

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 (170) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +1 -1
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +2 -21
  7. data/Gemfile.lock +60 -100
  8. data/Guardfile +1 -1
  9. data/LICENSE.md +21 -0
  10. data/README.md +63 -0
  11. data/Rakefile +3 -45
  12. data/bin/ki +1 -1
  13. data/ki.gemspec +31 -137
  14. data/lib/ki.rb +21 -21
  15. data/lib/ki/api_error.rb +28 -0
  16. data/lib/ki/base_request.rb +34 -0
  17. data/lib/ki/helpers.rb +55 -0
  18. data/lib/ki/ki.rb +40 -0
  19. data/lib/ki/ki_cli.rb +46 -0
  20. data/lib/ki/ki_config.rb +20 -0
  21. data/lib/ki/middleware.rb +155 -0
  22. data/lib/ki/model.rb +70 -0
  23. data/lib/{modules → ki/modules}/callbacks.rb +0 -0
  24. data/lib/ki/modules/format_of.rb +10 -0
  25. data/lib/{modules → ki/modules}/model_helpers.rb +0 -12
  26. data/lib/ki/modules/public_file_helper.rb +13 -0
  27. data/lib/ki/modules/query_interface.rb +36 -0
  28. data/lib/{modules → ki/modules}/restrictions.rb +0 -30
  29. data/lib/ki/modules/view_helper.rb +13 -0
  30. data/lib/ki/orm.rb +65 -0
  31. data/lib/ki/version.rb +3 -0
  32. data/spec/examples/base/.ruby-gemset +1 -0
  33. data/spec/examples/base/.ruby-version +1 -0
  34. data/spec/examples/base/Gemfile +4 -0
  35. data/spec/examples/base/app.rb +1 -0
  36. data/{examples → spec/examples}/base/config.ru +0 -3
  37. data/spec/examples/base/config.yml +17 -0
  38. data/spec/examples/base/public/favicon.ico +0 -0
  39. data/spec/examples/base/public/javascripts/.gitkeep +0 -0
  40. data/spec/examples/base/public/stylesheets/.gitkeep +0 -0
  41. data/spec/examples/base/views/index.haml +6 -0
  42. data/spec/examples/couch-lock/.ruby-gemset +1 -0
  43. data/spec/examples/couch-lock/.ruby-version +1 -0
  44. data/spec/examples/couch-lock/Gemfile +4 -0
  45. data/spec/examples/couch-lock/Gemfile.lock +48 -0
  46. data/spec/examples/couch-lock/app.rb +42 -0
  47. data/spec/examples/couch-lock/config.ru +3 -0
  48. data/spec/examples/couch-lock/config.yml +17 -0
  49. data/spec/examples/couch-lock/install.sh +3 -0
  50. data/spec/examples/couch-lock/public/favicon.ico +0 -0
  51. data/spec/examples/couch-lock/public/javascripts/.gitkeep +0 -0
  52. data/spec/examples/couch-lock/public/stylesheets/.gitkeep +0 -0
  53. data/spec/examples/couch-lock/views/index.haml +18 -0
  54. data/spec/examples/json.northpole.ro/.ruby-gemset +1 -0
  55. data/spec/examples/json.northpole.ro/.ruby-version +1 -0
  56. data/spec/examples/json.northpole.ro/Capfile +25 -0
  57. data/spec/examples/json.northpole.ro/Gemfile +6 -0
  58. data/spec/examples/json.northpole.ro/Gemfile.lock +68 -0
  59. data/spec/examples/json.northpole.ro/app.rb +79 -0
  60. data/spec/examples/json.northpole.ro/client.rb +25 -0
  61. data/spec/examples/json.northpole.ro/config.ru +3 -0
  62. data/spec/examples/json.northpole.ro/config.yml +17 -0
  63. data/spec/examples/json.northpole.ro/config/deploy.rb +59 -0
  64. data/spec/examples/json.northpole.ro/config/deploy/production.rb +39 -0
  65. data/spec/examples/json.northpole.ro/foo.rb +10 -0
  66. data/spec/examples/json.northpole.ro/public/JNorthPole.jar +0 -0
  67. data/spec/examples/json.northpole.ro/public/favicon.ico +0 -0
  68. data/spec/examples/json.northpole.ro/public/font/FontAwesome.otf +0 -0
  69. data/spec/examples/json.northpole.ro/public/font/fontawesome-webfont.eot +0 -0
  70. data/spec/examples/json.northpole.ro/public/font/fontawesome-webfont.svg +284 -0
  71. data/spec/examples/json.northpole.ro/public/font/fontawesome-webfont.ttf +0 -0
  72. data/spec/examples/json.northpole.ro/public/font/fontawesome-webfont.woff +0 -0
  73. data/spec/examples/json.northpole.ro/public/images/bear.png +0 -0
  74. data/spec/examples/json.northpole.ro/public/images/bg.png +0 -0
  75. data/spec/examples/json.northpole.ro/public/images/cloud.png +0 -0
  76. data/spec/examples/json.northpole.ro/public/images/dotnet.png +0 -0
  77. data/spec/examples/json.northpole.ro/public/images/dotnet_small.png +0 -0
  78. data/spec/examples/json.northpole.ro/public/images/footer.png +0 -0
  79. data/spec/examples/json.northpole.ro/public/images/glyphicons-halflings-white.png +0 -0
  80. data/spec/examples/json.northpole.ro/public/images/glyphicons-halflings.png +0 -0
  81. data/spec/examples/json.northpole.ro/public/images/ice.png +0 -0
  82. data/spec/examples/json.northpole.ro/public/images/java.png +0 -0
  83. data/spec/examples/json.northpole.ro/public/images/java_small.png +0 -0
  84. data/spec/examples/json.northpole.ro/public/images/json.png +0 -0
  85. data/spec/examples/json.northpole.ro/public/images/json_small.png +0 -0
  86. data/spec/examples/json.northpole.ro/public/images/logo.png +0 -0
  87. data/spec/examples/json.northpole.ro/public/images/mobile.png +0 -0
  88. data/spec/examples/json.northpole.ro/public/images/php.png +0 -0
  89. data/spec/examples/json.northpole.ro/public/images/php_small.png +0 -0
  90. data/spec/examples/json.northpole.ro/public/images/python.png +0 -0
  91. data/spec/examples/json.northpole.ro/public/images/python_small.png +0 -0
  92. data/spec/examples/json.northpole.ro/public/images/ruby.png +0 -0
  93. data/spec/examples/json.northpole.ro/public/images/ruby_small.png +0 -0
  94. data/spec/examples/json.northpole.ro/public/images/shell.png +0 -0
  95. data/spec/examples/json.northpole.ro/public/images/shell_small.png +0 -0
  96. data/spec/examples/json.northpole.ro/public/javascripts/analytics.js +9 -0
  97. data/spec/examples/json.northpole.ro/public/javascripts/app.coffee +133 -0
  98. data/spec/examples/json.northpole.ro/public/javascripts/docs.min.js +16 -0
  99. data/spec/examples/json.northpole.ro/public/javascripts/jnorthpole.coffee +34 -0
  100. data/spec/examples/json.northpole.ro/public/javascripts/util.coffee +31 -0
  101. data/spec/examples/json.northpole.ro/public/stylesheets/app.sass +23 -0
  102. data/spec/examples/json.northpole.ro/public/stylesheets/application.sass +222 -0
  103. data/spec/examples/json.northpole.ro/public/stylesheets/cover.css +154 -0
  104. data/spec/examples/json.northpole.ro/views/curl.haml +19 -0
  105. data/spec/examples/json.northpole.ro/views/doc.haml +16 -0
  106. data/spec/examples/json.northpole.ro/views/doc_table.haml +45 -0
  107. data/spec/examples/json.northpole.ro/views/dotnet.haml +24 -0
  108. data/spec/examples/json.northpole.ro/views/examples.haml +28 -0
  109. data/spec/examples/json.northpole.ro/views/faq.haml +32 -0
  110. data/spec/examples/json.northpole.ro/views/foo.haml +7 -0
  111. data/spec/examples/json.northpole.ro/views/index.haml +4 -0
  112. data/spec/examples/json.northpole.ro/views/java.haml +0 -0
  113. data/spec/examples/json.northpole.ro/views/layout.haml +42 -0
  114. data/spec/examples/json.northpole.ro/views/php.haml +37 -0
  115. data/spec/examples/json.northpole.ro/views/playground.haml +38 -0
  116. data/spec/examples/json.northpole.ro/views/python.haml +17 -0
  117. data/spec/examples/json.northpole.ro/views/ruby.haml +12 -0
  118. data/spec/examples/json.northpole.ro/views/signup.haml +2 -0
  119. data/spec/examples/json.northpole.ro/views/thanks.haml +12 -0
  120. data/spec/functional_spec.rb +4 -0
  121. data/spec/lib/ki/base_request_spec.rb +88 -0
  122. data/spec/lib/ki/helpers_spec.rb +12 -0
  123. data/spec/lib/ki/ki_config_spec.rb +7 -0
  124. data/spec/lib/ki/middleware_spec.rb +11 -0
  125. data/spec/lib/ki/model_spec.rb +100 -0
  126. data/spec/lib/ki/modules/format_of_spec.rb +15 -0
  127. data/spec/lib/ki/orm_spec.rb +83 -0
  128. data/spec/spec_helper.rb +18 -5
  129. data/spec/util_spec.rb +7 -36
  130. metadata +269 -193
  131. data/.document +0 -5
  132. data/.rvmrc +0 -1
  133. data/LICENSE.txt +0 -20
  134. data/README.rdoc +0 -3
  135. data/VERSION +0 -1
  136. data/examples/base/Gemfile +0 -3
  137. data/examples/base/Gemfile.lock +0 -24
  138. data/examples/base/app.rb +0 -4
  139. data/examples/base/config.yml +0 -16
  140. data/gfx/colors.xcf +0 -0
  141. data/gfx/ki3.eps +0 -0
  142. data/gfx/logo.png +0 -0
  143. data/ki_wrap.rb +0 -35
  144. data/lib/conf.rb +0 -28
  145. data/lib/db.rb +0 -73
  146. data/lib/helpers.rb +0 -34
  147. data/lib/ki_cli.rb +0 -76
  148. data/lib/mockreq.rb +0 -45
  149. data/lib/model.rb +0 -110
  150. data/lib/modules/email.rb +0 -51
  151. data/lib/modules/query_interface.rb +0 -42
  152. data/lib/req.rb +0 -57
  153. data/lib/resp.rb +0 -70
  154. data/lib/static_file.rb +0 -68
  155. data/lib/util.rb +0 -114
  156. data/lib/views/404.haml +0 -1
  157. data/lib/views/406.haml +0 -1
  158. data/lib/views/index.haml +0 -7
  159. data/lib/views/layout.haml +0 -1
  160. data/spec/conf_spec.rb +0 -6
  161. data/spec/db_spec.rb +0 -93
  162. data/spec/integration/integration_spec.rb +0 -114
  163. data/spec/ki_spec.rb +0 -7
  164. data/spec/model_spec.rb +0 -91
  165. data/spec/modules/callbacks_spec.rb +0 -25
  166. data/spec/modules/email_spec.rb +0 -19
  167. data/spec/modules/query_interface_spec.rb +0 -93
  168. data/spec/modules/restrictions_spec.rb +0 -110
  169. data/spec/performance/performance.sh +0 -1
  170. data/spec/req_spec.rb +0 -29
data/lib/ki.rb CHANGED
@@ -1,28 +1,28 @@
1
- require 'rack'
1
+ require 'yaml'
2
+ require 'uri'
2
3
 
4
+ require 'rack'
5
+ require 'rack/parser'
3
6
  require 'haml'
4
7
  require 'sass'
5
8
  require 'coffee-script'
9
+ require 'mongo'
10
+
6
11
 
7
- require 'conf'
8
- require 'db'
9
- require 'model'
10
- require 'helpers'
11
- require 'util'
12
- require 'req'
13
- require 'resp'
14
- require 'static_file'
15
- require 'mockreq'
12
+ require 'ki/api_error'
16
13
 
17
- Dir['public/**/**'].each do |s|
18
- Ki::StaticFile.compile_css(s)
19
- Ki::StaticFile.compile_js(s)
20
- end
14
+ require 'ki/modules/query_interface'
15
+ require 'ki/modules/restrictions'
16
+ require 'ki/modules/callbacks'
17
+ require 'ki/modules/model_helpers'
18
+ require 'ki/modules/view_helper'
19
+ require 'ki/modules/format_of'
20
+ require 'ki/modules/public_file_helper'
21
21
 
22
- module Ki
23
- class Ki
24
- def call env
25
- Req.new(env).serve
26
- end
27
- end
28
- end
22
+ require 'ki/ki_config'
23
+ require 'ki/helpers'
24
+ require 'ki/orm'
25
+ require 'ki/ki'
26
+ require 'ki/model'
27
+ require 'ki/middleware'
28
+ require 'ki/base_request'
@@ -0,0 +1,28 @@
1
+ module Ki
2
+ class ApiError < StandardError
3
+ attr_reader :status
4
+
5
+ def initialize body, status=400
6
+ super body
7
+ @status = status
8
+ end
9
+
10
+ def result
11
+ { 'error' => to_s }
12
+ end
13
+ end
14
+
15
+ class InvalidUrlError < ApiError; end
16
+ class RequiredAttributeMissing < ApiError; end
17
+ class AttributeNotUnique < ApiError; end
18
+ class ForbiddenAction < ApiError
19
+ def initialize
20
+ super 'action forbidden', 400
21
+ end
22
+ end
23
+ class PartialNotFoundError < ApiError
24
+ def initialize s
25
+ super "partial #{s} not found", 404
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ module Ki
2
+ class BaseRequest < Rack::Request
3
+ include FormatOf
4
+
5
+ def root?
6
+ path == '/'
7
+ end
8
+
9
+ def json?
10
+ content_type == 'application/json' || format_of(path) == 'json'
11
+ end
12
+
13
+ def to_ki_model_class
14
+ self.path.to_s.gsub('/','').gsub(format_of(path), '').gsub('.', '').to_class
15
+ end
16
+
17
+ def to_action
18
+ case request_method
19
+ when 'GET'
20
+ :find
21
+ when 'POST'
22
+ :create
23
+ when 'PUT'
24
+ :update
25
+ when 'DELETE'
26
+ :delete
27
+ when 'SEARCH'
28
+ :find
29
+ else
30
+ raise 'unkown action'
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,55 @@
1
+ class String
2
+ def to_class
3
+ chain = self.split "::"
4
+ klass = Kernel
5
+ chain.each do |klass_string|
6
+ klass = klass.const_get klass_string.capitalize
7
+ end
8
+ klass.is_a?(Class) ? klass : nil
9
+ rescue NameError
10
+ nil
11
+ end
12
+ end
13
+
14
+ class Array
15
+ def stringify_ids
16
+ self.collect do |e|
17
+ if e['_id']
18
+ e['id'] = e['_id'].to_s
19
+ e.delete('_id')
20
+ end
21
+ if e[:_id]
22
+ e['id'] = e[:_id].to_s
23
+ e.delete(:_id)
24
+ end
25
+ e
26
+ end
27
+ end
28
+ end
29
+
30
+ module Ki
31
+ module Helpers
32
+ include ViewHelper
33
+
34
+ def css url
35
+ render_haml "%link{:href => '#{url}', :rel => 'stylesheet'}"
36
+ end
37
+
38
+ def js url
39
+ render_haml "%script{:src => '#{url}'}"
40
+ end
41
+
42
+ def render_haml s
43
+ Haml::Engine.new(s).render
44
+ end
45
+
46
+ def partial s
47
+ path = view_path(s)
48
+ if File.file?(path)
49
+ render_haml(File.read(path))
50
+ else
51
+ raise PartialNotFoundError.new path
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,40 @@
1
+ module Ki
2
+ class KiApp
3
+ # should never reach this
4
+ # middleware should catch all the requests
5
+ def call env
6
+ s = 'misplaced in space'
7
+ Rack::Response.new(s).finish
8
+ end
9
+ end
10
+
11
+ class Ki
12
+ PUBLIC_PATH = 'public'
13
+ VIEWS_PATH = 'views'
14
+
15
+ def initialize
16
+ KiConfig.instance.read environment
17
+ Orm::Db.instance.establish_connection
18
+
19
+ @app = Rack::Builder.new do
20
+ use Middleware::InitMiddleware
21
+ # TODO what happens with invalid json?
22
+ use Rack::Parser, :content_types => { 'application/json' => Proc.new { |body| ::MultiJson.decode body } }
23
+ use Middleware::ApiHandler
24
+ use Middleware::CoffeeCompiler
25
+ use Middleware::SassCompiler
26
+ use Middleware::HamlCompiler
27
+ use Middleware::PublicFileServer
28
+ run KiApp.new
29
+ end
30
+ end
31
+
32
+ def environment
33
+ ENV['RACK_ENV']
34
+ end
35
+
36
+ def call env
37
+ @app.call env
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ require 'thor'
2
+
3
+ class KiGenerator < Thor::Group
4
+ include Thor::Actions
5
+
6
+ def self.source_root
7
+ File.join(File.dirname(__FILE__), '..', '..')
8
+ end
9
+ end
10
+
11
+ class AppGenerator < KiGenerator
12
+
13
+ argument :app_name
14
+
15
+ def prepare_dir
16
+ unless app_name =~ /^[a-zA-Z0-9-]*$/
17
+ say "App name must contain only alphanumeric characters and -"
18
+ exit 1
19
+ end
20
+
21
+ if Dir.exists? app_name
22
+ say "#{app_name} already exists"
23
+ exit 2
24
+ end
25
+
26
+ Dir.mkdir app_name
27
+ end
28
+
29
+ def create_app
30
+ directory("spec/examples/base", app_name)
31
+
32
+ # Set database names
33
+ config_file = File.read("#{app_name}/config.yml")
34
+ config_file.gsub!("name: np_development", "name: #{app_name}_development")
35
+ config_file.gsub!("name: np_test", "name: #{app_name}_test")
36
+ config_file.gsub!("name: np", "name: #{app_name}")
37
+ File.open("#{app_name}/config.yml", "w") {|file| file.puts config_file}
38
+
39
+ # Set rvm gemset name
40
+ `echo #{app_name} > #{app_name}/.ruby-gemset`
41
+ end
42
+ end
43
+
44
+ class KiCli < Thor
45
+ register AppGenerator, :new, 'new [APP_NAME]', 'generate a new app'
46
+ end
@@ -0,0 +1,20 @@
1
+ require 'singleton'
2
+
3
+ module Ki
4
+ class KiConfig
5
+ include Singleton
6
+
7
+ CONFIG_FILE_PATH = 'config.yml'
8
+
9
+ attr_reader :config, :environment
10
+
11
+ def read environment
12
+ @environment = environment
13
+ @config = YAML.load_file(CONFIG_FILE_PATH)[environment]
14
+ end
15
+
16
+ def database
17
+ @config['database']
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,155 @@
1
+ module Ki
2
+ module Middleware
3
+ module BaseMiddleware
4
+ include FormatOf
5
+ include ViewHelper
6
+ include PublicFileHelper
7
+
8
+ def initialize app
9
+ @app = app
10
+ end
11
+ end
12
+
13
+ class PublicFileServer
14
+ include BaseMiddleware
15
+
16
+ def call env
17
+ req = BaseRequest.new env
18
+ if public_file_exists? req
19
+ Rack::File.new(Ki::PUBLIC_PATH).call env
20
+ else
21
+ @app.call env
22
+ end
23
+ end
24
+ end
25
+
26
+ class InitMiddleware
27
+ include BaseMiddleware
28
+
29
+ def call env
30
+ req = BaseRequest.new env
31
+ if req.root?
32
+ resp = Rack::Response.new
33
+ resp.redirect('/index')
34
+ resp.finish
35
+ else
36
+ env['CONTENT_TYPE'] = 'application/json' if format_of(req) == 'json'
37
+ @app.call env
38
+ end
39
+ end
40
+ end
41
+
42
+ class HamlCompiler
43
+ include BaseMiddleware
44
+
45
+ def call env
46
+ req = BaseRequest.new env
47
+ if view_exists?(req)
48
+ render_haml view_path(req)
49
+ else
50
+ @app.call env
51
+ end
52
+ end
53
+
54
+ def render_haml file_path
55
+ file_contents = File.read(file_path)
56
+
57
+ if view_exists? 'layout'
58
+ layout_contents = File.read(view_path('layout'))
59
+ else
60
+ layout_contents = "= yield"
61
+ end
62
+
63
+ html = haml(layout_contents).render do
64
+ haml(file_contents).render
65
+ end
66
+
67
+ Rack::Response.new(html).finish
68
+ end
69
+
70
+ def haml s
71
+ Haml::Engine.new("- extend Ki::Helpers\n" + s)
72
+ end
73
+ end
74
+
75
+ class CoffeeCompiler
76
+ include BaseMiddleware
77
+
78
+ def call env
79
+ req = BaseRequest.new env
80
+ coffee_path = req.path.to_s[0...-3] + '.coffee'
81
+ if !public_file_exists?(req) && format_of(req) == 'js' && public_file_exists?(coffee_path)
82
+ js = CoffeeScript.compile(File.read(public_file_path(coffee_path)))
83
+ Rack::Response.new(js).finish
84
+ else
85
+ @app.call env
86
+ end
87
+ end
88
+ end
89
+
90
+ class SassCompiler
91
+ include BaseMiddleware
92
+
93
+ def call env
94
+ req = BaseRequest.new env
95
+ sass_path = req.path.to_s[0...-4] + '.sass'
96
+ # if req ends with css and it does not exist, if a sass file exists instead
97
+ if !public_file_exists?(req) && format_of(req) == 'css' && public_file_exists?(sass_path)
98
+ eng = Sass::Engine.new(File.read(public_file_path(sass_path)), :syntax => :sass)
99
+ Rack::Response.new(eng.render).finish
100
+ else
101
+ @app.call env
102
+ end
103
+ end
104
+ end
105
+
106
+ # Handles all API calls
107
+ #
108
+ # Any json request is considered an api call. A request is considered as json
109
+ # if the format is .json or Content-Type header is set to 'application/json'
110
+ #
111
+ # If the query param 'redirect_to' is given, the response will not contain the
112
+ # json output from the url, instead it will redirect to the url given
113
+ class ApiHandler
114
+ include BaseMiddleware
115
+
116
+ def call env
117
+ req = BaseRequest.new env
118
+ if req.json?
119
+ begin
120
+ klass = req.to_ki_model_class
121
+ if Model.descendants.include? klass
122
+ # TODO do not have redirect_to param
123
+ model = klass.new(req.to_action, req.params)
124
+ # TODO document this
125
+ if req.params['redirect_to'].nil?
126
+ render model
127
+ else
128
+ # TODO check for injection
129
+ redirect_to req.params['redirect_to']
130
+ end
131
+ else
132
+ raise InvalidUrlError.new("invalid url '#{req.path}'", 404)
133
+ end
134
+ rescue ApiError => e
135
+ render e
136
+ end
137
+ else
138
+ @app.call env
139
+ end
140
+ end
141
+
142
+ def redirect_to s
143
+ resp = Rack::Response.new
144
+ resp.redirect(s)
145
+ resp.finish
146
+ end
147
+
148
+ def render r
149
+ resp = Rack::Response.new(r.result.to_json, r.status)
150
+ resp['Content-Type'] = 'application/json'
151
+ resp.finish
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,70 @@
1
+ module Ki
2
+ class Model
3
+ extend QueryInterface
4
+ extend Restrictions
5
+ include Callbacks
6
+ include ModelHelpers
7
+
8
+ attr_accessor :action, :result, :params, :status
9
+
10
+ def initialize action, params
11
+ @action = action
12
+ @params = params
13
+ @status = 200
14
+
15
+ raise ForbiddenAction.new if forbidden_actions.include? @action
16
+ ccall
17
+ end
18
+
19
+ def find
20
+ @result = self.class.find @params
21
+ end
22
+
23
+ def create
24
+ check_for_required_attributes
25
+ check_for_unique_attributes
26
+ @result = self.class.create @params
27
+ end
28
+
29
+ def update
30
+ check_for_required_attributes
31
+ check_for_unique_attributes
32
+ @result = self.class.update @params
33
+ end
34
+
35
+ def delete
36
+ @result = self.class.delete @params
37
+ end
38
+
39
+ private
40
+
41
+ def check_for_required_attributes
42
+ required_attributes.each do |ra|
43
+ if !@params.keys.include?(ra.to_s)
44
+ raise RequiredAttributeMissing.new("#{ra.to_s} missing")
45
+ end
46
+ end
47
+ end
48
+
49
+ def check_for_unique_attributes
50
+ unique_attributes.each do |ua|
51
+ u = self.class.find({ua.to_s => @params[ua.to_s]})
52
+ unless u.empty?
53
+ raise AttributeNotUnique.new("#{ua.to_s} not unique")
54
+ end
55
+ end
56
+ end
57
+
58
+ def ccall
59
+ before_all
60
+ send "before_#{@action.to_s}".to_sym
61
+ send @action.to_sym
62
+ send "after_#{@action.to_s}".to_sym
63
+ after_all
64
+ end
65
+
66
+ def self.descendants
67
+ ObjectSpace.each_object(Class).select { |klass| klass < self }
68
+ end
69
+ end
70
+ end