ki 0.3.3 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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