cfrederick-titanium 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Cody Frederick
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = What is titanium?
2
+
3
+ = How does titanium work?
4
+
5
+ = Getting started
data/lib/base.rb ADDED
@@ -0,0 +1,49 @@
1
+ # Copyright (c) 2009 Cody Frederick
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Titanium
22
+
23
+ class Base
24
+
25
+ protected
26
+
27
+ def log_error(progname=nil, &block)
28
+ Titanium::Configuration.logger.error(progname, &block)
29
+ end
30
+
31
+ def log_warn(progname=nil, &block)
32
+ Titanium::Configuration.logger.warn(progname, &block)
33
+ end
34
+
35
+ def log_info(progname=nil, &block)
36
+ Titanium::Configuration.logger.info(progname, &block)
37
+ end
38
+
39
+ def log_debug(progname=nil, &block)
40
+ Titanium::Configuration.logger.debug(progname, &block)
41
+ end
42
+
43
+ def environment
44
+ Titanium::Configuration.environment
45
+ end
46
+
47
+ end # Base
48
+
49
+ end # Titanium
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2009 Cody Frederick
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Titanium
22
+
23
+ module Configuration
24
+
25
+ def self.environment
26
+ @environment ||= ENV['RACK_ENV'] || 'development'
27
+ end
28
+
29
+ def self.environment=(value)
30
+ @environment = value
31
+ end
32
+
33
+ def self.logger
34
+ @logger ||= Logger.new(STDOUT)
35
+ end
36
+
37
+ def self.logger=(value)
38
+ @logger = value
39
+ end
40
+
41
+ def self.clean_start
42
+ @clean_start ||= false
43
+ end
44
+
45
+ def self.clean_start=(value)
46
+ @clean_start = value
47
+ end
48
+
49
+ def self.db_adapter
50
+ @db_adapter ||= SQLiteAdapter
51
+ end
52
+
53
+ def self.db_adapter=(value)
54
+ @db_adapter = value
55
+ end
56
+
57
+ end # Configuration
58
+
59
+ end # Titanium
data/lib/resource.rb ADDED
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2009 Cody Frederick
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Titanium
22
+
23
+ class Resource < Base
24
+ attr_reader :name, :key
25
+ attr_accessor :metadata
26
+
27
+ # Constructs a new Resource. Additional options can be provided as shown below.
28
+ # r = Resource.new("book")
29
+ # r = Resource.new("book", {:metadata => {:title => "Animal Farm"}})
30
+ # r = Resource.new("book", {:key => "128dfrit12wywbc", :metadata => {:title => "Animal Farm"}})
31
+ def initialize(name, options={})
32
+ @name = name
33
+ @key = options[:key]
34
+ @metadata = options[:metadata]
35
+ end
36
+
37
+ def to_json
38
+ json_hash = @metadata.dup
39
+ json_hash[:key] = @key
40
+ json_hash.to_json
41
+ end
42
+
43
+ def insert
44
+ db_instance = Configuration.db_adapter.new
45
+ begin
46
+ db_instance.connect
47
+ return db_instance.insert(@name, @metadata)
48
+ ensure
49
+ db_instance.disconnect
50
+ end
51
+ end
52
+
53
+ def update
54
+ db_instance = Configuration.db_adapter.new
55
+ begin
56
+ db_instance.connect
57
+ return db_instance.update(@key, @metadata)
58
+ ensure
59
+ db_instance.disconnect
60
+ end
61
+ end
62
+
63
+ def delete
64
+ db_instance = Configuration.db_adapter.new
65
+ begin
66
+ db_instance.connect
67
+ return db_instance.delete(@key)
68
+ ensure
69
+ db_instance.disconnect
70
+ end
71
+ end
72
+
73
+ def self.find_by_name(name, sort_field=nil, sort_order=nil, limit=nil, offset=nil)
74
+ db_instance = Configuration.db_adapter.new
75
+ begin
76
+ db_instance.connect
77
+ resources = []
78
+ result = db_instance.find_by_name_and_conditions(name, nil, nil, sort_field, sort_order, limit, offset)
79
+ result.each do |row|
80
+ key = row.delete('key')
81
+ resources << Resource.new(name, {:key => key, :metadata => row})
82
+ end
83
+ return resources
84
+ ensure
85
+ db_instance.disconnect
86
+ end
87
+ end
88
+
89
+ def self.count_by_name(name)
90
+ db_instance = Configuration.db_adapter.new
91
+ begin
92
+ db_instance.connect
93
+ count = db_instance.count_by_name(name)
94
+ return count
95
+ ensure
96
+ db_instance.disconnect
97
+ end
98
+ end
99
+
100
+ def self.delete_all_by_name(name)
101
+ db_instance = Configuration.db_adapter.new
102
+ begin
103
+ db_instance.connect
104
+ return db_instance.delete_all_by_name(name)
105
+ ensure
106
+ db_instance.disconnect
107
+ end
108
+ end
109
+
110
+ def self.find_by_name_and_key(name, key)
111
+ db_instance = Configuration.db_adapter.new
112
+ begin
113
+ db_instance.connect
114
+ resources = []
115
+ result = db_instance.find_by_name_and_conditions(name, key)
116
+ result.each do |row|
117
+ key = row.delete('key')
118
+ resources << Resource.new(name, {:key => key, :metadata => row})
119
+ end
120
+ return resources
121
+ ensure
122
+ db_instance.disconnect
123
+ end
124
+ end
125
+
126
+ def self.find_by_name_and_params(name, params, sort_field=nil, sort_order=nil, limit=nil, offset=nil)
127
+ db_instance = Configuration.db_adapter.new
128
+ begin
129
+ db_instance.connect
130
+ resources = []
131
+ result = db_instance.find_by_name_and_conditions(name, nil, params, sort_field, sort_order, limit, offset)
132
+ result.each do |row|
133
+ key = row.delete('key')
134
+ resources << Resource.new(name, {:key => key, :metadata => row})
135
+ end
136
+ return resources
137
+ ensure
138
+ db_instance.disconnect
139
+ end
140
+ end
141
+
142
+ end # Resource
143
+
144
+ end # Titanium
data/lib/server.rb ADDED
@@ -0,0 +1,262 @@
1
+ # Copyright (c) 2009 Cody Frederick
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'rubygems'
22
+ require 'rack'
23
+ require 'json/pure'
24
+
25
+ module Titanium
26
+
27
+ class Server < Base
28
+ include Rack::Utils
29
+
30
+ # Constructs a new Application. This effectively starts the entire web server process which also includes
31
+ # initializing all other downstream processes such as the database layer.
32
+ def initialize(root)
33
+ @app_root = root
34
+ @file_server = Rack::File.new(@app_root)
35
+ erase_db if Configuration.clean_start
36
+ start_db
37
+ end
38
+
39
+ def erase_db
40
+ db_instance = Configuration.db_adapter.new
41
+ begin
42
+ db_instance.erase_db
43
+ rescue Exception => ex
44
+ log_error(ex)
45
+ return nil
46
+ ensure
47
+ db_instance.disconnect
48
+ end
49
+ end
50
+
51
+ def start_db
52
+ db_instance = Configuration.db_adapter.new
53
+ begin
54
+ db_instance.connect
55
+ db_instance.startup
56
+ rescue Exception => ex
57
+ log_error(ex)
58
+ return nil
59
+ ensure
60
+ db_instance.disconnect
61
+ end
62
+ end
63
+
64
+ # Handles the Rack request.
65
+ def call(env)
66
+ begin
67
+ create_rack_request(env)
68
+ if is_valid_request?
69
+ if is_static_request?
70
+ serve_static_request
71
+ else
72
+ serve_dynamic_request
73
+ end
74
+ else
75
+ serve_bad_request
76
+ end
77
+ rescue Exception => ex
78
+ log_error(ex)
79
+ serve_internal_server_error
80
+ end
81
+ end
82
+
83
+ protected
84
+
85
+ # Constructs a new Rack::Request. This also updates the path_info to the index.html page if no resource is
86
+ # requested.
87
+ def create_rack_request(env)
88
+ @request = Rack::Request.new(env)
89
+ @request.path_info = '/index.html' if @request.path_info == '/'
90
+ end
91
+
92
+ # Determines if the request is valid.
93
+ # * Checks the http method verb
94
+ def is_valid_request?
95
+ is_valid_method?
96
+ end
97
+
98
+ # Determines if the http request method is acceptable. Only GET, POST, PUT, and DELETE are acceptable.
99
+ def is_valid_method?
100
+ case @request.request_method
101
+ when 'GET', 'POST', 'PUT', 'DELETE' then true
102
+ else false
103
+ end
104
+ end
105
+
106
+ # Determines if the request is for a static resource. All static requests must be for a resource with a file
107
+ # extension
108
+ def is_static_request?
109
+ get_resource_from_request =~ /\..*$/
110
+ end
111
+
112
+ # Forms a static file path from the request's path information.
113
+ def get_static_path_from_request
114
+ "#{@app_root}#{@request.path_info}"
115
+ end
116
+
117
+ # Serves a 400 status code.
118
+ def serve_bad_request
119
+ serve_error_code_response(400, "Bad Request")
120
+ end
121
+
122
+ # Serves a 404 status code.
123
+ def serve_not_found
124
+ serve_error_code_response(404, "Not Found")
125
+ end
126
+
127
+ # Serves a 500 status code.
128
+ def serve_internal_server_error
129
+ serve_error_code_response(500, "Internal Server Error")
130
+ end
131
+
132
+ # Serves an error page (or default message if the page cannot be found) for a given response code
133
+ def serve_error_code_response(code, message)
134
+ if File.exist?("#{@app_root}/#{code}.html")
135
+ @request.path_info = "/#{code}.html"
136
+ @file_server.call(@request.env)
137
+ else
138
+ [code, { 'Content-Type' => 'text/plain' }, message]
139
+ end
140
+ end
141
+
142
+ # Extracts the resource requested. The resource equals the entire path info minus the query string
143
+ def get_resource_from_request
144
+ @request.path_info.split("@").first
145
+ end
146
+
147
+ # Extracts the key of the resource requested.
148
+ # /application/model/book => nil
149
+ # /library/book@4 => 4
150
+ def get_key_from_request
151
+ @request.path_info.include?("@") ? @request.path_info.split("@").last : nil
152
+ end
153
+
154
+ # Serves a static request.
155
+ def serve_static_request
156
+ if File.exist?(get_static_path_from_request)
157
+ @file_server.call(@request.env)
158
+ else
159
+ serve_not_found
160
+ end
161
+ end
162
+
163
+ # Serves a dynamic request.
164
+ def serve_dynamic_request
165
+ response_hash = self.send(@request.request_method.strip.downcase)
166
+ @response = Rack::Response.new
167
+ @response.headers['Content-Type'] = "application/json"
168
+ @response.write(response_hash.to_json)
169
+ @response.finish
170
+ end
171
+
172
+ # Updates a resource in the datastore. Called from serve_dynamic_request.
173
+ def put
174
+ resource_name = get_resource_from_request
175
+ resource_key = get_key_from_request
176
+ metadata = parse_query(@request.body.read)
177
+ if resource_key
178
+ resources = Resource.find_by_name_and_key(resource_name, resource_key)
179
+ if resources && resources.first
180
+ resource = resources.first
181
+ resource.metadata = metadata
182
+ key = resource.update
183
+ if key
184
+ {:status => :success, :key => key}
185
+ else
186
+ {:status => :failed, :key => nil}
187
+ end
188
+ else
189
+ {:status => :failed, :message => "Unable to find resource with the key: #{resource_key}"}
190
+ end
191
+ else
192
+ serve_bad_request
193
+ end
194
+ end
195
+
196
+ # Retreives a list of resources. Called from serve_dynamic_request.
197
+ def get
198
+ resource_name = get_resource_from_request
199
+ resource_key = get_key_from_request
200
+ params = @request.GET
201
+ sort_field = params.delete('sort-field')
202
+ sort_order = params.delete('sort-order')
203
+ limit = params.delete('limit')
204
+ offset = params.delete('offset')
205
+ if resource_key
206
+ if resource_key == "_count"
207
+ count = Resource.count_by_name(resource_name)
208
+ else
209
+ resources = Resource.find_by_name_and_key(resource_name, resource_key)
210
+ end
211
+ else
212
+ if params.nil? or params.empty?
213
+ resources = Resource.find_by_name(resource_name, sort_field, sort_order, limit, offset)
214
+ else
215
+ resources = Resource.find_by_name_and_params(resource_name, params, sort_field, sort_order, limit, offset)
216
+ end
217
+ end
218
+ {:status => :success, :entities => resources, :count => count || resources.length}
219
+ end
220
+
221
+ # Inserts the resource into the datastore. Called from serve_dynamic_request.
222
+ def post
223
+ resource_name = get_resource_from_request
224
+ metadata = @request.POST
225
+ metadata.delete("key")
226
+ resource = Resource.new(resource_name, {:metadata => metadata})
227
+ key = resource.insert
228
+ if key
229
+ {:status => :success, :key => key}
230
+ else
231
+ {:status => :failed, :key => nil}
232
+ end
233
+ end
234
+
235
+ # Deletes a resource in the datastore. Called from serve_dynamic_request.
236
+ def delete
237
+ resource_name = get_resource_from_request
238
+ resource_key = get_key_from_request
239
+ if resource_key
240
+ if resource_key == "_all"
241
+ if Resource.delete_all_by_name(resource_name)
242
+ {:status => :success}
243
+ else
244
+ {:status => :failed, :message => "Failed to delete all entities of type: #{resource_name}"}
245
+ end
246
+ else
247
+ resources = Resource.find_by_name_and_key(resource_name, resource_key)
248
+ if resources && resources.first
249
+ resource = resources.first
250
+ resource.delete ? {:status => :success} : {:status => :failed}
251
+ else
252
+ {:status => :failed, :message => "Unable to find resource with the key: #{resource_key}"}
253
+ end
254
+ end
255
+ else
256
+ serve_bad_request
257
+ end
258
+ end
259
+
260
+ end # Server
261
+
262
+ end # Titanium
@@ -0,0 +1,337 @@
1
+ # Copyright (c) 2009 Cody Frederick
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'rubygems'
22
+ require 'time'
23
+ require 'sqlite3'
24
+
25
+ include SQLite3
26
+
27
+ module Titanium
28
+
29
+ class SQLiteAdapter < Base
30
+ attr_reader :db_file
31
+
32
+ def initialize
33
+ @db_file = "data/#{environment}.db"
34
+ end
35
+
36
+ def erase_db
37
+ File.delete(File.expand_path(@db_file))
38
+ end
39
+
40
+ def connect
41
+ @db = Database.new(@db_file)
42
+ @db.results_as_hash = true
43
+ end
44
+
45
+ def disconnect
46
+ if @db
47
+ @db.close unless @db.closed?
48
+ end
49
+ end
50
+
51
+ def startup
52
+ @db.transaction do
53
+ @db.execute(sql_create_types_table)
54
+ @db.execute(sql_create_resources_table)
55
+ @db.execute(sql_create_attributes_table)
56
+ end
57
+ end
58
+
59
+ def insert(name, metadata)
60
+ resource_key = nil
61
+ @db.transaction do
62
+ type_id = @db.get_first_value(sql_find_type_id_from_type_name, :name => name)
63
+ if type_id.nil?
64
+ @db.execute(sql_insert_type, :name => name)
65
+ type_id = @db.last_insert_row_id
66
+ end
67
+ @db.execute(sql_insert_resource, :type_id => type_id)
68
+ resource_key = @db.last_insert_row_id
69
+ now = Time.now.iso8601
70
+ metadata['created_at'] = now
71
+ metadata['updated_at'] = now
72
+ metadata.each do |key,value|
73
+ @db.execute(sql_insert_attribute, :resource_key => resource_key, :key => key, :value => value)
74
+ end
75
+ end
76
+ return resource_key
77
+ end
78
+
79
+ def update(resource_key, metadata)
80
+ @db.transaction do
81
+ @db.execute(sql_delete_attributes_by_resource_key, :resource_key => resource_key)
82
+ metadata['created_at'] ||= Time.now.iso8601
83
+ metadata['updated_at'] = Time.now.iso8601
84
+ metadata.each do |key,value|
85
+ @db.execute(sql_insert_attribute, :resource_key => resource_key, :key => key, :value => value)
86
+ end
87
+ end
88
+ return resource_key
89
+ end
90
+
91
+ def delete(resource_key)
92
+ @db.transaction do
93
+ resources_remaining = @db.get_first_value(sql_count_of_resources_by_resource_key, :resource_key => resource_key)
94
+ @db.execute(sql_delete_attributes_by_resource_key, :resource_key => resource_key)
95
+ @db.execute(sql_delete_type_by_resource_key, :resource_key => resource_key) if resources_remaining.to_i == 1
96
+ @db.execute(sql_delete_resource_by_resource_key, :resource_key => resource_key)
97
+ end
98
+ return true
99
+ end
100
+
101
+ def count_by_name(name)
102
+ return @db.get_first_value(sql_count_of_resources_by_type_name, :name => name)
103
+ end
104
+
105
+ def delete_all_by_name(name)
106
+ @db.transaction do
107
+ @db.execute_batch(sql_delete_resources_by_type_name, :name => name)
108
+ end
109
+ return true
110
+ end
111
+
112
+ def find_by_name_and_conditions(name, key=nil, conditions=nil, sort_field=nil, sort_order=nil, limit=nil, offset=nil)
113
+ results = {}
114
+ query_id = generate_query_id
115
+
116
+ @db.transaction do
117
+ index = 0
118
+ @db.execute(sql_drop_sorter_table(query_id))
119
+ @db.execute(sql_create_sorter_table(query_id, name, key, conditions, sort_field, sort_order, limit, offset))
120
+ @db.execute(sql_query_sorter_table(query_id)) do |row|
121
+ build_results_hash(results, index, row)
122
+ index = index + 1
123
+ end
124
+ @db.execute(sql_drop_sorter_table(query_id))
125
+ end
126
+
127
+ return build_results_array(results)
128
+ end
129
+
130
+ private
131
+
132
+ def generate_query_id
133
+ values = [rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x0010000), rand(0x1000000), rand(0x1000000)]
134
+ "%04x%04x%04x%04x%04x%06x%06x" % values
135
+ end
136
+
137
+ def sql_drop_sorter_table(query_id)
138
+ "DROP TABLE IF EXISTS tmp_#{query_id}"
139
+ end
140
+
141
+ def sql_create_sorter_table(query_id, name, key=nil, conditions=nil, sort_field=nil, sort_order=nil, limit=nil, offset=nil)
142
+ # resolving variables below instead of in the argument signature because there is a difference between
143
+ # not supplying the argument and providing a nil value. this adapter relies on resolving nil values
144
+ # for the arguments in the signature.
145
+ key = key ? "AND r.id = '#{key}'" : ""
146
+ sort_field ||="created_at"
147
+ sort_order ||= "ASC"
148
+ limit ||= "-1"
149
+ offset ||="0"
150
+ sql = ""
151
+
152
+ if conditions
153
+ sql = "%subquery%"
154
+ conditions.each_pair do |attr_key,attr_value|
155
+ conditions_sql = <<-SQL
156
+ SELECT DISTINCT r.id
157
+ FROM resources r
158
+ INNER JOIN attributes a ON a.resource_id = r.id
159
+ WHERE a.key = '#{attr_key}' AND a.value = '#{attr_value}'
160
+ AND r.id IN (
161
+ %subquery%
162
+ )
163
+ SQL
164
+ sql.gsub!("%subquery%", conditions_sql)
165
+ end
166
+ typesql = <<-SQL
167
+ SELECT r.id
168
+ FROM resources r
169
+ INNER JOIN types t ON t.id = r.type_id
170
+ WHERE t.name = '#{name}'
171
+ #{key}
172
+ SQL
173
+ sql.gsub!("%subquery%", typesql)
174
+ else
175
+ sql = <<-SQL
176
+ SELECT r.id
177
+ FROM resources r
178
+ INNER JOIN types t ON t.id = r.type_id
179
+ WHERE t.name = '#{name}'
180
+ #{key}
181
+ SQL
182
+ end
183
+
184
+ sorter_table_sql = <<-SQL
185
+ CREATE TEMP TABLE tmp_#{query_id} AS
186
+ SELECT r.id as resource_id, a.value
187
+ FROM resources r
188
+ INNER JOIN attributes a ON a.resource_id = r.id
189
+ WHERE a.key = '#{sort_field}'
190
+ AND r.id IN (
191
+ %subquery%
192
+ )
193
+ ORDER BY a.value #{sort_order}
194
+ LIMIT #{limit}
195
+ OFFSET #{offset}
196
+ SQL
197
+ sorter_table_sql.gsub!("%subquery%", sql)
198
+
199
+ return sorter_table_sql
200
+ end
201
+
202
+ def sql_query_sorter_table(query_id)
203
+ <<-SQL
204
+ SELECT
205
+ r.id AS key,
206
+ a.key AS attr_key,
207
+ a.value AS attr_value
208
+ FROM resources r
209
+ INNER JOIN attributes a ON a.resource_id = r.id
210
+ INNER JOIN tmp_#{query_id} s ON s.resource_id = r.id
211
+ ORDER BY s.ROWID ASC;
212
+ SQL
213
+ end
214
+
215
+ def build_results_hash(results, index, row)
216
+ key = row['key']
217
+ attr_key = row['attr_key']
218
+ attr_value = row['attr_value']
219
+ results[key] ||= {}
220
+ results[key]['query_index'] ||= index
221
+ results[key]['key'] ||= key
222
+ results[key][attr_key] ||= attr_value
223
+ end
224
+
225
+ def build_results_array(results)
226
+ sorted_results = results.sort { |a,b| a[1]['query_index'] <=> b[1]['query_index'] }
227
+ results_array = []
228
+ sorted_results.each do |item|
229
+ item[1].delete('query_index')
230
+ results_array << item[1]
231
+ end
232
+ return results_array
233
+ end
234
+
235
+ def sql_create_types_table
236
+ <<-SQL
237
+ CREATE TABLE IF NOT EXISTS types (
238
+ id INTEGER PRIMARY KEY NOT NULL,
239
+ name TEXT NOT NULL
240
+ );
241
+ SQL
242
+ end
243
+
244
+ def sql_create_resources_table
245
+ <<-SQL
246
+ CREATE TABLE IF NOT EXISTS resources (
247
+ id INTEGER PRIMARY KEY NOT NULL,
248
+ type_id INTEGER REFERENCES types(id)
249
+ );
250
+ SQL
251
+ end
252
+
253
+ def sql_create_attributes_table
254
+ <<-SQL
255
+ CREATE TABLE IF NOT EXISTS attributes (
256
+ id INTEGER PRIMARY KEY NOT NULL,
257
+ resource_id INTEGER REFERENCES resources(id),
258
+ key TEXT NOT NULL,
259
+ value TEXT
260
+ );
261
+ SQL
262
+ end
263
+
264
+ def sql_insert_type
265
+ "INSERT INTO types (name) VALUES (:name)"
266
+ end
267
+
268
+ def sql_insert_resource
269
+ "INSERT INTO resources (type_id) VALUES (:type_id)"
270
+ end
271
+
272
+ def sql_insert_attribute
273
+ "INSERT INTO attributes (resource_id, key, value) VALUES (:resource_key, :key, :value)"
274
+ end
275
+
276
+ def sql_delete_attributes_by_resource_key
277
+ "DELETE FROM attributes WHERE resource_id = :resource_key"
278
+ end
279
+
280
+ def sql_delete_resource_by_resource_key
281
+ "DELETE FROM resources WHERE id = :resource_key"
282
+ end
283
+
284
+ def sql_delete_type_by_resource_key
285
+ <<-SQL
286
+ DELETE FROM types
287
+ WHERE id IN (
288
+ SELECT type_id FROM resources WHERE id = :resource_key
289
+ )
290
+ SQL
291
+ end
292
+
293
+ def sql_count_of_resources_by_resource_key
294
+ <<-SQL
295
+ SELECT COUNT(*)
296
+ FROM resources
297
+ WHERE type_id IN (
298
+ SELECT type_id FROM resources WHERE id = :resource_key
299
+ )
300
+ SQL
301
+ end
302
+
303
+ def sql_count_of_resources_by_type_name
304
+ <<-SQL
305
+ SELECT COUNT(*)
306
+ FROM resources r
307
+ INNER JOIN types t ON t.id = r.type_id
308
+ WHERE t.name = :name
309
+ SQL
310
+ end
311
+
312
+ def sql_delete_resources_by_type_name
313
+ <<-SQL
314
+ DELETE FROM attributes
315
+ WHERE resource_id IN (
316
+ SELECT r.id
317
+ FROM resources r
318
+ INNER JOIN types t ON t.id = r.type_id
319
+ WHERE t.name = :name
320
+ );
321
+
322
+ DELETE FROM resources
323
+ WHERE type_id IN (
324
+ SELECT id FROM types WHERE name = :name
325
+ );
326
+
327
+ DELETE FROM types WHERE name = :name;
328
+ SQL
329
+ end
330
+
331
+ def sql_find_type_id_from_type_name
332
+ "SELECT id FROM types WHERE name = :name"
333
+ end
334
+
335
+ end # SQLiteAdapter
336
+
337
+ end # Titanium
data/lib/titanium.rb ADDED
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2009 Cody Frederick
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'logger'
22
+ require 'base'
23
+ require 'configuration'
24
+ require 'server'
25
+ require 'resource'
26
+ require 'sqlite_adapter'
27
+
28
+ module Titanium
29
+ VERSION = "0.5.1"
30
+ end # Titanium
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cfrederick-titanium
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.1
5
+ platform: ruby
6
+ authors:
7
+ - Cody Frederick
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-16 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rack
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "1.0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: sqlite3-ruby
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.4
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: json_pure
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.1.7
44
+ version:
45
+ description: Lightweight but powerful RESTful application server
46
+ email: cody@codingsouthpaw.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.rdoc
53
+ - LICENSE
54
+ files:
55
+ - README.rdoc
56
+ - lib/base.rb
57
+ - lib/configuration.rb
58
+ - lib/resource.rb
59
+ - lib/server.rb
60
+ - lib/sqlite_adapter.rb
61
+ - lib/titanium.rb
62
+ - LICENSE
63
+ has_rdoc: true
64
+ homepage: https://github.com/cfrederick/titanium
65
+ licenses:
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --line-numbers
69
+ - --inline-source
70
+ - --title
71
+ - titanium
72
+ - --main
73
+ - README.rdoc
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ version:
88
+ requirements: []
89
+
90
+ rubyforge_project:
91
+ rubygems_version: 1.3.5
92
+ signing_key:
93
+ specification_version: 2
94
+ summary: Lightweight but powerful RESTful application server
95
+ test_files: []
96
+