gazebo 0.1.2

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.
@@ -0,0 +1,15 @@
1
+ class LimitClause
2
+ attr_reader :num
3
+
4
+ def initialize(num = nil)
5
+ @num = num
6
+ end
7
+
8
+ def as_sql
9
+ num.nil? ? "" : "LIMIT #{num} "
10
+ end
11
+
12
+ def set(n)
13
+ @num = n
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ class OrderClause
2
+ attr_accessor :ordering_attr
3
+
4
+ def initialize(ordering_attr = nil)
5
+ @ordering_attr = ordering_attr
6
+ end
7
+
8
+ def as_sql
9
+ return "" if ordering_attr.nil?
10
+ "ORDER BY #{ordering_attr.to_s}"
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ class SelectClause
2
+ attr_accessor :distinct, :params
3
+
4
+ def initialize(params = [])
5
+ @distinct = false
6
+ @params = params
7
+ end
8
+
9
+ def as_sql
10
+ "SELECT " +
11
+ "#{distinct ? 'DISTINCT ' : ''}" +
12
+ "#{params_as_sql}"
13
+ end
14
+
15
+ def params_as_sql
16
+ params.map(&:to_s).join(', ')
17
+ end
18
+ end
@@ -0,0 +1,61 @@
1
+ require_relative '../errors'
2
+
3
+ class WhereClause
4
+ def initialize(options = [])
5
+ unless options.is_a?(Array) && options.length <= 2
6
+ raise InvalidInput, "Where takes 1 or 2 arguments"
7
+ end
8
+
9
+ @conditions = parse_conditions(options)
10
+ end
11
+
12
+ def values
13
+ conditions.values.flatten
14
+ end
15
+
16
+ def as_sql
17
+ return "" if conditions.empty?
18
+ " WHERE " + conditions_as_sql
19
+ end
20
+
21
+ def append(options)
22
+ conditions.merge!(parse_conditions(options))
23
+ end
24
+
25
+ alias_method :<<, :append
26
+
27
+ private
28
+ attr_reader :bind_params, :conditions
29
+
30
+ def conditions_as_sql
31
+ conditions.map do |condition, values|
32
+ if condition.is_a?(String)
33
+ format_condition(condition, values)
34
+ elsif condition.is_a?(Symbol)
35
+ "#{condition} = ?"
36
+ end
37
+ end.flatten.join(' AND ').gsub("?").with_index { |_, i| "$#{i + 1}" }
38
+ end
39
+
40
+ def parse_params(params_input)
41
+ return [] if params_input.nil?
42
+ params_input.is_a?(Array) ? params_input : [params_input]
43
+ end
44
+
45
+ def parse_conditions(input)
46
+ if input.first.is_a?(String)
47
+ { input.first => parse_params(input[1]) }
48
+ elsif input.first.is_a?(Hash)
49
+ input.first
50
+ else
51
+ {}
52
+ end
53
+ end
54
+
55
+ def format_condition(condition, values)
56
+ condition.gsub('(?)') do
57
+ "(#{values.map.with_index { |_, i| "$#{i + 1}" }})"
58
+ # "(#{(['?'] * values.count).join(', ')})"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,106 @@
1
+ class Relation
2
+ include Enumerable
3
+
4
+ attr_reader :query, :cache, :source_class, :data
5
+
6
+ def defaults
7
+ {
8
+ select: SelectClause.new(["#{source_class.table_name}.*"]),
9
+ from: FromClause.new(source_class.table_name),
10
+ join: JoinOptions.new,
11
+ where: WhereClause.new,
12
+ limit: LimitClause.new,
13
+ group: GroupClause.new,
14
+ order: OrderClause.new
15
+ }
16
+ end
17
+
18
+ def self.ordered_clauses
19
+ [:select, :from, :join, :where, :group, :order, :limit]
20
+ end
21
+
22
+ def initialize(query, source_class)
23
+ @source_class = source_class
24
+ @query = defaults.merge(query)
25
+ @cache = nil
26
+ end
27
+
28
+ def where(*where_params)
29
+ query[:where] << where_params
30
+ empty_cache!
31
+ self
32
+ end
33
+
34
+ def joins(association, join_class = source_class)
35
+ options = join_class.assoc_options[association]
36
+ query[:join].append(options, join_class.table_name)
37
+ empty_cache!
38
+ self
39
+ end
40
+
41
+ def limit(n)
42
+ query[:limit].set(n)
43
+ empty_cache!
44
+ self
45
+ end
46
+
47
+ def select(*params)
48
+ query[:select].params = params
49
+ empty_cache!
50
+ self
51
+ end
52
+
53
+ def distinct
54
+ query[:select].distinct = true
55
+ empty_cache!
56
+ self
57
+ end
58
+
59
+ def group(grouping_attr)
60
+ query[:group].grouping_attr = grouping_attr
61
+ empty_cache!
62
+ self
63
+ end
64
+
65
+ def order(ordering_attr)
66
+ query[:order].ordering_attr = ordering_attr
67
+ empty_cache!
68
+ self
69
+ end
70
+
71
+ def as_sql
72
+ Relation.ordered_clauses.map do |clause|
73
+ query[clause].as_sql
74
+ end.join(" \n ")
75
+ end
76
+
77
+ def data
78
+ execute! if cache.nil?
79
+ cache
80
+ end
81
+
82
+ def each(&prc)
83
+ to_a.each { |el| prc.call(el) }
84
+ end
85
+
86
+ def bind_params
87
+ query[:where].values
88
+ end
89
+
90
+ def to_a
91
+ execute! if cache.nil?
92
+ cache.map { |datum| source_class.new(datum) }
93
+ end
94
+
95
+ def execute!
96
+ @cache = DBConnection.execute(as_sql, bind_params)
97
+ end
98
+
99
+ def inspect
100
+ p to_a
101
+ end
102
+
103
+ def empty_cache!
104
+ @cache = nil
105
+ end
106
+ end
@@ -0,0 +1,213 @@
1
+ class SQLObject
2
+ def self.after_initialize(method_name)
3
+ callbacks[:after_initialize] = method_name
4
+ end
5
+
6
+ def self.all
7
+ Relation.new({}, self)
8
+ end
9
+
10
+ def self.callbacks
11
+ @callbacks ||= {}
12
+ end
13
+
14
+ def self.columns
15
+ return @columns if @columns
16
+
17
+ cols = DBConnection.execute(<<-SQL, [self.table_name])
18
+ SELECT
19
+ column_name
20
+ FROM
21
+ information_schema.columns
22
+ WHERE
23
+ table_name = $1
24
+ SQL
25
+
26
+ @columns = cols.map { |c| c['column_name'].to_sym }
27
+ end
28
+
29
+ def self.destroy_all
30
+ DBConnection.execute(<<-SQL)
31
+ DELETE FROM #{self.table_name}
32
+ SQL
33
+ end
34
+
35
+ def self.finalize!
36
+ self.columns.each do |col|
37
+ define_method(col) do #setter method
38
+ attributes[col]
39
+ end
40
+
41
+ define_method("#{col}=") do |val| #getter method
42
+ attributes[col] = val
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.first
48
+ first_data = DBConnection.get_first_row(<<-SQL)
49
+ SELECT
50
+ *
51
+ FROM
52
+ #{self.table_name}
53
+ ORDER BY
54
+ id
55
+ LIMIT
56
+ 1
57
+ SQL
58
+
59
+ self.new(first_data)
60
+ end
61
+
62
+ def self.last
63
+ last_data = DBConnection.get_first_row(<<-SQL)
64
+ SELECT
65
+ *
66
+ FROM
67
+ #{self.table_name}
68
+ ORDER BY
69
+ id DESC
70
+ LIMIT
71
+ 1
72
+ SQL
73
+
74
+ self.new(last_data)
75
+ end
76
+
77
+ def self.parse_all(all_options)
78
+ all_options.map { |options| self.new(options) }
79
+ end
80
+
81
+ def self.table_name=(table_name)
82
+ @table_name = table_name
83
+ end
84
+
85
+ def self.table_name
86
+ @table_name ||= self.to_s.tableize.gsub('humen') { 'humans' }
87
+ end
88
+
89
+ def self.validations
90
+ @validations ||= []
91
+ end
92
+
93
+ def attributes
94
+ @attributes ||= {}
95
+ end
96
+
97
+ def attr_count
98
+ @attributes.count
99
+ end
100
+
101
+ def attribute_values
102
+ attributes.values
103
+ end
104
+
105
+ def attr_values_to_update
106
+ attributes.reject { |attr_name, _| attr_name == :id }.values
107
+ end
108
+
109
+ def col_names
110
+ self.class.columns
111
+ .map(&:to_s)
112
+ .drop(1).join(', ')
113
+ end
114
+
115
+ def destroy
116
+ DBConnection.execute(<<-SQL, [self.id])
117
+ DELETE FROM #{self.class.table_name}
118
+ WHERE id = $1
119
+ SQL
120
+ end
121
+
122
+ def errors
123
+ @errors ||= Hash.new { |h, k| h[k] = [] }
124
+ end
125
+
126
+ def initialize(params = {})
127
+ self.class.columns.each do |attr_name|
128
+ params_val = params[attr_name] || params[attr_name.to_s]
129
+ params_val.strip! if params_val
130
+ send("#{attr_name}=", params_val)
131
+ end
132
+
133
+ params.each do |attr_name, val|
134
+ attr_method = "#{attr_name}="
135
+ next if self.class.columns.include?(attr_name.to_sym)
136
+ next unless self.respond_to?(attr_method)
137
+
138
+ val.strip! if val
139
+ send(attr_method, val)
140
+ end
141
+
142
+ if self.class.callbacks[:after_initialize]
143
+ send(self.class.callbacks[:after_initialize])
144
+ end
145
+ end
146
+
147
+ def insert
148
+ result = DBConnection.execute(<<-SQL, attr_values_to_update)
149
+ INSERT INTO
150
+ #{self.class.table_name} (#{col_names})
151
+ VALUES
152
+ (#{question_marks})
153
+ RETURNING
154
+ id
155
+ SQL
156
+
157
+ self.id = result.getvalue(0,0)
158
+ end
159
+
160
+ def question_marks
161
+ (1...attributes.count).map { |n| "$#{n}"}.join(', ')
162
+ end
163
+
164
+ def save
165
+ validate!
166
+ if valid?
167
+ id ? update : insert
168
+ true
169
+ else
170
+ show_errors
171
+ false
172
+ end
173
+ end
174
+
175
+ def show_errors
176
+ errors.each do |key, messages|
177
+ messages.each { |m| puts "#{key} #{m}" }
178
+ end
179
+ end
180
+
181
+ def to_s
182
+ "#{self.class}:#{self.object_id}"
183
+ end
184
+
185
+ def update_set_line
186
+ col_names.split(', ').map.with_index { |c, i| "#{c} = $#{i + 1}" }.join(', ')
187
+ end
188
+
189
+ def update
190
+ vals = attr_values_to_update << id
191
+ DBConnection.execute(<<-SQL, vals)
192
+ UPDATE
193
+ #{self.class.table_name}
194
+ SET
195
+ #{update_set_line}
196
+ WHERE
197
+ id = $#{vals.length}
198
+ SQL
199
+ end
200
+
201
+ def validate!
202
+ @errors = Hash.new { |h, k| h[k] = [] }
203
+
204
+ self.class.validations.each do |validation|
205
+ self.send(validation)
206
+ end
207
+ end
208
+
209
+ def valid?
210
+ validate!
211
+ errors.all? { |_, v| v.empty? }
212
+ end
213
+ end
@@ -0,0 +1,31 @@
1
+ module Gazebo
2
+ LOAD_PATHS = [
3
+ "app/models",
4
+ "app/controllers"
5
+ ]
6
+ end
7
+
8
+ class Object
9
+ def self.const_missing(const)
10
+ auto_load(const)
11
+ Kernel.const_get(const)
12
+ end
13
+
14
+ def auto_load(const)
15
+ Gazebo::LOAD_PATHS.each do |dir|
16
+ file = File.join(Gazebo::ROOT, dir, const.to_s.underscore)
17
+
18
+ begin
19
+ require_relative(file)
20
+ return
21
+ rescue LoadError => e
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ class Module
28
+ def const_missing(const)
29
+ Object.const_missing(const)
30
+ end
31
+ end
data/lib/cli.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'thor'
2
+
3
+ module Gazebo
4
+ class DatabaseTasks < Thor
5
+ desc "migrate", "run any previously unexecuted migrations"
6
+ def migrate
7
+ DBConnection.run_migrations
8
+ end
9
+
10
+ desc "seed", "seed database"
11
+ def seed
12
+ Gazebo.seed
13
+ end
14
+ end
15
+
16
+ end
data/lib/gazebo.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'byebug'
2
+ require 'rack'
3
+ require 'uri'
4
+
5
+ require_relative 'activeleopard/activeleopard'
6
+ require_relative 'actioncondor/actioncondor'
7
+ require_relative 'static_asset_server'
8
+ require_relative 'show_exceptions'
9
+ require_relative 'auto_loader'
10
+ require_relative 'router'
11
+ require_relative 'cli'
12
+
13
+ module Gazebo
14
+ Router = Router.new
15
+ VERSION = "0.1.2"
16
+
17
+ def self.app
18
+ fetch_routes
19
+
20
+ app = Proc.new do |env|
21
+ req = Rack::Request.new(env)
22
+ res = Rack::Response.new
23
+ Gazebo::Router.run(req, res)
24
+ res
25
+ end
26
+
27
+ app = Rack::Builder.new do
28
+ use ShowExceptions
29
+ use StaticAssetServer
30
+ run app
31
+ end.to_app
32
+ end
33
+
34
+ def self.root=(root)
35
+ const_set("ROOT", root)
36
+ end
37
+
38
+ def self.fetch_routes
39
+ file = File.join(ROOT, "config/routes.rb")
40
+
41
+ File.open(file) do |f|
42
+ self.class_eval(f.read)
43
+ end
44
+ end
45
+
46
+ def self.seed
47
+ file = File.join(ROOT, "db/seeds.rb")
48
+
49
+ File.open(file) do |f|
50
+ self.class_eval(f.read)
51
+ end
52
+ end
53
+ end
data/lib/router.rb ADDED
@@ -0,0 +1,78 @@
1
+ class Route
2
+ attr_reader :pattern, :http_method, :controller_class, :action_name
3
+
4
+ def initialize(pattern, http_method, controller_class, action_name)
5
+ @pattern = pattern
6
+ @http_method = http_method
7
+ @controller_class = controller_class
8
+ @action_name = action_name
9
+ end
10
+
11
+ # checks if pattern matches path and method matches request method
12
+ def matches?(req)
13
+ return false if (pattern =~ req.path).nil?
14
+
15
+ # check if form is providing non-get/post method
16
+ req_method = req.params["_method"] || req.request_method
17
+
18
+ req_method.upcase == http_method.to_s.upcase
19
+ end
20
+
21
+ # use pattern to pull out route params (save for later?)
22
+ # instantiate controller and call controller action
23
+ def run(req, res)
24
+ matched_params = pattern.match(req.path)
25
+
26
+ params = {}
27
+ matched_params.names.each do |param_key|
28
+ params[param_key] = matched_params[param_key]
29
+ end
30
+
31
+ controller_class.new(req, res, params).invoke_action(action_name)
32
+ end
33
+ end
34
+
35
+ class Router
36
+ attr_reader :routes
37
+
38
+ def initialize
39
+ @routes = []
40
+ end
41
+
42
+ # simply adds a new route to the list of routes
43
+ def add_route(pattern, method, controller_class, action_name)
44
+ route = Route.new(pattern, method, controller_class, action_name)
45
+ @routes << route
46
+ end
47
+
48
+ # evaluate the proc in the context of the instance
49
+ # for syntactic sugar :)
50
+ def draw(&proc)
51
+ self.instance_eval(&proc)
52
+ end
53
+
54
+ # make each of these methods that
55
+ # when called add route
56
+ [:get, :post, :put, :delete].each do |http_method|
57
+ define_method(http_method) do |pattern, controller_class, action_name|
58
+ add_route(pattern, http_method, controller_class, action_name)
59
+ end
60
+ end
61
+
62
+ # should return the route that matches this request
63
+ def match(req)
64
+ routes.find { |route| route.matches?(req) }
65
+ end
66
+
67
+ # either throw 404 or call run on a matched route
68
+ def run(req, res)
69
+ matched_route = match(req)
70
+
71
+ if matched_route
72
+ res.status = 200
73
+ matched_route.run(req, res)
74
+ else
75
+ res.status = 404
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,28 @@
1
+ class ShowExceptions
2
+ attr_reader :app, :error
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ begin
10
+ @app.call(env)
11
+ rescue StandardError => e
12
+ render_exception(e)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def render_exception(e)
19
+ res = Rack::Response.new
20
+ file_content = File.read("#{File.dirname(__FILE__)}/templates/rescue.html.erb")
21
+ content = ERB.new(file_content).result(binding)
22
+
23
+ res['Content-Type'] = 'text/html'
24
+ res.status = 500
25
+ res.write(content)
26
+ res.finish
27
+ end
28
+ end
@@ -0,0 +1,57 @@
1
+ class StaticAssetServer
2
+ attr_reader :file_server, :app, :root
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ @root = 'app/assets/'
7
+ @file_server = FileServer.new
8
+ end
9
+
10
+ def call(env)
11
+ req = Rack::Request.new(env)
12
+ path = req.path
13
+
14
+ if servable?(path)
15
+ res = file_server.call(env)
16
+ else
17
+ res = app.call(env)
18
+ end
19
+ res.finish
20
+ end
21
+
22
+ def servable?(path)
23
+ path.match("#{root}")
24
+ end
25
+ end
26
+
27
+ class FileServer
28
+ def call(env)
29
+ res = Rack::Response.new
30
+ file_name = requested_file_name(env)
31
+
32
+ if File.exists?(file_name)
33
+ serve(file_name, res)
34
+ else
35
+ res.status = 404
36
+ res.write("File not found")
37
+ end
38
+ res
39
+ end
40
+
41
+ def serve(file_name, res)
42
+ extension = File.extname(file_name)
43
+ extension = '.json' if extension == '.map'
44
+ content_type = Rack::Mime::MIME_TYPES[extension]
45
+
46
+ res['Content-Type'] = content_type
47
+ file = File.read(file_name)
48
+ res.write(file)
49
+ end
50
+
51
+ def requested_file_name(env)
52
+ req = Rack::Request.new(env)
53
+ path = req.path
54
+ dir = File.dirname(__FILE__)
55
+ File.join(dir, '..', path)
56
+ end
57
+ end
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title></title>
6
+ </head>
7
+ <body>
8
+ <h1><%= e.class %></h1>
9
+ <h2>Message: <%= e.message %></h2>
10
+
11
+ <h3>Backtrace:</h3>
12
+ <ul>
13
+ <% e.backtrace.take(10).each do |trace| %>
14
+ <li><%= trace %></li>
15
+ <% end %>
16
+ </ul>
17
+ </body>
18
+ </html>