cfrederick-titanium 0.5.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.
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
+