vundabar 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +237 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/README.md +36 -0
- data/Rakefile +6 -0
- data/bin/setup +8 -0
- data/bin/vundabar +13 -0
- data/circle.yml +8 -0
- data/generators/generators.rb +72 -0
- data/generators/templates/Gemfile +4 -0
- data/generators/templates/application.html.erb +10 -0
- data/generators/templates/application.rb +6 -0
- data/generators/templates/config.ru +6 -0
- data/generators/templates/invalid_route.html.erb +38 -0
- data/generators/templates/routes.rb +14 -0
- data/lib/vundabar/controller.rb +69 -0
- data/lib/vundabar/dependencies.rb +6 -0
- data/lib/vundabar/orm/associations.rb +24 -0
- data/lib/vundabar/orm/base_model.rb +121 -0
- data/lib/vundabar/orm/database.rb +12 -0
- data/lib/vundabar/orm/model_helper.rb +85 -0
- data/lib/vundabar/orm/validations.rb +5 -0
- data/lib/vundabar/routing/mapper.rb +30 -0
- data/lib/vundabar/routing/route.rb +18 -0
- data/lib/vundabar/routing/routing.rb +56 -0
- data/lib/vundabar/utilities.rb +19 -0
- data/lib/vundabar/version.rb +3 -0
- data/lib/vundabar.rb +45 -0
- data/vundabar.gemspec +44 -0
- metadata +286 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
module Vundabar
|
2
|
+
class BaseModel
|
3
|
+
include Vundabar::ModelHelper
|
4
|
+
extend Vundabar::Associations
|
5
|
+
def initialize(attributes = {})
|
6
|
+
attributes.each { |column, value| send("#{column}=", value) }
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_reader :properties
|
11
|
+
private(
|
12
|
+
:make_methods,
|
13
|
+
:build_table_fields,
|
14
|
+
:parse_constraint,
|
15
|
+
:type,
|
16
|
+
:primary_key,
|
17
|
+
:nullable,
|
18
|
+
:get_model_object
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.to_table(name)
|
23
|
+
@table = name.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.property(column_name, column_properties)
|
27
|
+
@properties ||= {}
|
28
|
+
@properties[column_name] = column_properties
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.create_table
|
32
|
+
query = "CREATE TABLE IF NOT EXISTS #{table_name} "\
|
33
|
+
"(#{build_table_fields(@properties).join(', ')})"
|
34
|
+
Database.execute_query(query)
|
35
|
+
make_methods
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(attributes)
|
39
|
+
table = self.class.table_name
|
40
|
+
query = "UPDATE #{table} SET #{update_placeholders(attributes)}"\
|
41
|
+
" WHERE id= ?"
|
42
|
+
Database.execute_query(query, update_values(attributes))
|
43
|
+
end
|
44
|
+
|
45
|
+
def destroy
|
46
|
+
table = self.class.table_name
|
47
|
+
Database.execute_query "DELETE FROM #{table} WHERE id= ?", id
|
48
|
+
end
|
49
|
+
|
50
|
+
def save
|
51
|
+
table = self.class.table_name
|
52
|
+
query = if id
|
53
|
+
"UPDATE #{table} SET #{update_placeholders} WHERE id = ?"
|
54
|
+
else
|
55
|
+
"INSERT INTO #{table} (#{table_columns}) VALUES "\
|
56
|
+
"(#{record_placeholders})"
|
57
|
+
end
|
58
|
+
values = id ? record_values << send("id") : record_values
|
59
|
+
Database.execute_query query, values
|
60
|
+
end
|
61
|
+
|
62
|
+
alias save! save
|
63
|
+
|
64
|
+
def self.all
|
65
|
+
query = "SELECT * FROM #{table_name} "\
|
66
|
+
"ORDER BY id DESC"
|
67
|
+
result = Database.execute_query query
|
68
|
+
result.map { |row| get_model_object(row) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.create(attributes)
|
72
|
+
model_object = new(attributes)
|
73
|
+
model_object.save
|
74
|
+
id = Database.execute_query "SELECT last_insert_rowid()"
|
75
|
+
model_object.id = id.first.first
|
76
|
+
model_object
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.count
|
80
|
+
result = Database.execute_query "SELECT COUNT(*) FROM #{table_name}"
|
81
|
+
result.first.first
|
82
|
+
end
|
83
|
+
|
84
|
+
[%w(last DESC), %w(first ASC)].each do |method_name_and_order|
|
85
|
+
define_singleton_method((method_name_and_order[0]).to_sym) do
|
86
|
+
query = "SELECT * FROM #{table_name} ORDER BY "\
|
87
|
+
"id #{method_name_and_order[1]} LIMIT 1"
|
88
|
+
row = Database.execute_query query
|
89
|
+
get_model_object(row.first) unless row.empty?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.find(id)
|
94
|
+
query = "SELECT * FROM #{table_name} "\
|
95
|
+
"WHERE id= ?"
|
96
|
+
row = Database.execute_query(query, id).first
|
97
|
+
get_model_object(row) if row
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.find_by(option)
|
101
|
+
query = "SELECT * FROM #{table_name} "\
|
102
|
+
"WHERE #{option.keys.first}= ?"
|
103
|
+
row = Database.execute_query(query, option.values.first).first
|
104
|
+
get_model_object(row) if row
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.destroy(id)
|
108
|
+
Database.execute_query "DELETE FROM #{table_name} WHERE id= ?", id
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.destroy_all
|
112
|
+
Database.execute_query "DELETE FROM #{table_name}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.where(query_string, value)
|
116
|
+
data = Database.execute_query "SELECT * FROM "\
|
117
|
+
"#{table_name} WHERE #{query_string}", value
|
118
|
+
data.map { |row| get_model_object(row) }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Vundabar
|
2
|
+
module ModelHelper
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def table_columns
|
10
|
+
columns = self.class.properties.keys
|
11
|
+
columns.delete(:id)
|
12
|
+
columns.map(&:to_s).join(", ")
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_placeholders(attributes = self.class.properties)
|
16
|
+
columns = attributes.keys
|
17
|
+
columns.delete(:id)
|
18
|
+
columns.map { |column| "#{column}= ?" }.join(", ")
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_values(attributes)
|
22
|
+
attributes.values << id
|
23
|
+
end
|
24
|
+
|
25
|
+
def record_placeholders
|
26
|
+
(["?"] * (self.class.properties.keys.size - 1)).join(",")
|
27
|
+
end
|
28
|
+
|
29
|
+
def record_values
|
30
|
+
column_names = self.class.properties.keys
|
31
|
+
column_names.delete(:id)
|
32
|
+
column_names.map { |column_name| send(column_name) }
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
def table_name
|
37
|
+
@table
|
38
|
+
end
|
39
|
+
|
40
|
+
def make_methods
|
41
|
+
properties.keys.each do |column_name|
|
42
|
+
attr_accessor column_name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_table_fields(properties)
|
47
|
+
all_properties = []
|
48
|
+
properties.each do |field_name, constraints|
|
49
|
+
column = []
|
50
|
+
column << field_name.to_s
|
51
|
+
parse_constraint(constraints, column)
|
52
|
+
all_properties << column.join(" ")
|
53
|
+
end
|
54
|
+
all_properties
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_constraint(constraints, column)
|
58
|
+
constraints.each do |attribute, value|
|
59
|
+
column << send(attribute.to_s, value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def type(value)
|
64
|
+
value.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
def primary_key(value)
|
68
|
+
"PRIMARY KEY AUTOINCREMENT" if value
|
69
|
+
end
|
70
|
+
|
71
|
+
def nullable(value)
|
72
|
+
"NOT NULL" unless value
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_model_object(row)
|
76
|
+
return nil unless row
|
77
|
+
model_name = new
|
78
|
+
properties.keys.each_with_index do |key, index|
|
79
|
+
model_name.send("#{key}=", row[index])
|
80
|
+
end
|
81
|
+
model_name
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Vundabar
|
2
|
+
module Routing
|
3
|
+
class Mapper
|
4
|
+
attr_reader :endpoints, :request
|
5
|
+
def initialize(endpoints)
|
6
|
+
@endpoints = endpoints
|
7
|
+
end
|
8
|
+
|
9
|
+
def find_route(request)
|
10
|
+
@request = request
|
11
|
+
path = request.path_info
|
12
|
+
method = request.request_method.downcase.to_sym
|
13
|
+
endpoints[method].detect do |endpoint|
|
14
|
+
match_path_with_endpoint path, endpoint
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def match_path_with_endpoint(path, endpoint)
|
19
|
+
regex, placeholders = endpoint[:pattern]
|
20
|
+
if regex =~ path
|
21
|
+
match_data = Regexp.last_match
|
22
|
+
placeholders.each do |placeholder|
|
23
|
+
request.update_param(placeholder, match_data[placeholder])
|
24
|
+
end
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Vundabar
|
2
|
+
module Routing
|
3
|
+
class Route
|
4
|
+
attr_reader :controller_name, :action, :request
|
5
|
+
def initialize(request, klass_and_method)
|
6
|
+
@request = request
|
7
|
+
@controller_name, @action = klass_and_method
|
8
|
+
end
|
9
|
+
|
10
|
+
def dispatcher
|
11
|
+
controller = controller_name.to_constant.new(request)
|
12
|
+
controller.send(action)
|
13
|
+
controller.render(action) unless controller.get_response
|
14
|
+
controller.get_response
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Vundabar
|
2
|
+
module Routing
|
3
|
+
class Router
|
4
|
+
attr_accessor :endpoints
|
5
|
+
def draw(&block)
|
6
|
+
instance_eval(&block)
|
7
|
+
end
|
8
|
+
|
9
|
+
[:get, :post, :delete, :put, :patch].each do |method|
|
10
|
+
define_method(method) do |path, options|
|
11
|
+
path = "/#{path}" unless path[0] == "/"
|
12
|
+
route_info = {
|
13
|
+
path: path,
|
14
|
+
klass_and_method: controller_and_action(options[:to]),
|
15
|
+
pattern: pattern_for(path)
|
16
|
+
}
|
17
|
+
endpoints[method] << route_info
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def root(address)
|
22
|
+
get "/", to: address
|
23
|
+
end
|
24
|
+
|
25
|
+
def resources(args)
|
26
|
+
args = args.to_s
|
27
|
+
get("/#{args}", to: "#{args}#index")
|
28
|
+
get("/#{args}/new", to: "#{args}#new")
|
29
|
+
get("/#{args}/:id", to: "#{args}#show")
|
30
|
+
get("/#{args}/:id/edit", to: "#{args}#edit")
|
31
|
+
delete("/#{args}/:id", to: "#{args}#destroy")
|
32
|
+
post("/#{args}/create", to: "#{args}#create")
|
33
|
+
put("/#{args}/:id", to: "#{args}#update")
|
34
|
+
end
|
35
|
+
|
36
|
+
def pattern_for(path)
|
37
|
+
placeholders = []
|
38
|
+
new_path = path.gsub(/(:\w+)/) do |match|
|
39
|
+
placeholders << match[1..-1]
|
40
|
+
"(?<#{placeholders.last}>[^?/#]+)"
|
41
|
+
end
|
42
|
+
[/^#{new_path}$/, placeholders]
|
43
|
+
end
|
44
|
+
|
45
|
+
def endpoints
|
46
|
+
@endpoints ||= Hash.new { |hash, key| hash[key] = [] }
|
47
|
+
end
|
48
|
+
|
49
|
+
def controller_and_action(to)
|
50
|
+
controller, action = to.split('#')
|
51
|
+
controller = "#{controller.to_camel_case}Controller"
|
52
|
+
[controller, action]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class String
|
2
|
+
def to_snake_case
|
3
|
+
gsub!(/::/, "/")
|
4
|
+
gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
5
|
+
gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
6
|
+
tr!("-", "_")
|
7
|
+
downcase!
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_camel_case
|
12
|
+
return self if self !~ /_/ && self =~ /[A-Z]+.*/
|
13
|
+
split("_").map(&:capitalize).join
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_constant
|
17
|
+
Object.const_get(self)
|
18
|
+
end
|
19
|
+
end
|
data/lib/vundabar.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "vundabar/version"
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require "vundabar/utilities"
|
4
|
+
require "vundabar/dependencies"
|
5
|
+
require "vundabar/routing/routing"
|
6
|
+
require "vundabar/routing/mapper"
|
7
|
+
require "vundabar/routing/route"
|
8
|
+
require "vundabar/controller"
|
9
|
+
require "pry"
|
10
|
+
require "sqlite3"
|
11
|
+
require "vundabar/orm/database"
|
12
|
+
require "vundabar/orm/model_helper"
|
13
|
+
require "vundabar/orm/associations"
|
14
|
+
require "vundabar/orm/base_model"
|
15
|
+
module Vundabar
|
16
|
+
class Application
|
17
|
+
attr_reader :routes
|
18
|
+
def initialize
|
19
|
+
@routes = Routing::Router.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
request = Rack::Request.new(env)
|
24
|
+
route = mapper.find_route(request)
|
25
|
+
if route
|
26
|
+
call_controller_and_action(request, route[:klass_and_method])
|
27
|
+
else
|
28
|
+
invalid_route_processor(request)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def mapper
|
33
|
+
@mapper ||= Routing::Mapper.new(routes.endpoints)
|
34
|
+
end
|
35
|
+
|
36
|
+
def call_controller_and_action(request, klass_and_method)
|
37
|
+
Routing::Route.new(request, klass_and_method).dispatcher
|
38
|
+
end
|
39
|
+
|
40
|
+
def invalid_route_processor(request)
|
41
|
+
response = BaseController.new(request).invalid_route(routes.endpoints)
|
42
|
+
[404, {}, [response]]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/vundabar.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "vundabar/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "vundabar"
|
8
|
+
spec.version = Vundabar::VERSION
|
9
|
+
spec.authors = ["Eyiowuawi Olalekan"]
|
10
|
+
spec.email = ["olalekan.eyiowuawi@andela.com"]
|
11
|
+
|
12
|
+
spec.summary = "A simple rack-based MVC framework"
|
13
|
+
spec.description = "A simple rack-based MVC framework that can be used to build awesome webapps."
|
14
|
+
spec.homepage = "https://www.github.com/andela-oeyiowuawi/Vundabar"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
17
|
+
# delete this section to allow pushing this gem to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
20
|
+
else
|
21
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
22
|
+
end
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
spec.bindir = "bin"
|
26
|
+
spec.executables = ["vundabar"]
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
32
|
+
spec.add_runtime_dependency "rack", "~> 1.0"
|
33
|
+
spec.add_runtime_dependency "thor"
|
34
|
+
spec.add_runtime_dependency "activesupport"
|
35
|
+
spec.add_development_dependency "rack-test", "~> 0.6"
|
36
|
+
spec.add_development_dependency "capybara"
|
37
|
+
spec.add_development_dependency "faker"
|
38
|
+
spec.add_development_dependency "selenium-webdriver"
|
39
|
+
spec.add_development_dependency "factory_girl"
|
40
|
+
spec.add_runtime_dependency "pry"
|
41
|
+
spec.add_development_dependency "coveralls"
|
42
|
+
spec.add_runtime_dependency "sqlite3"
|
43
|
+
spec.add_runtime_dependency "tilt"
|
44
|
+
end
|