vundabar 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,12 @@
1
+ require "sqlite3"
2
+ module Vundabar
3
+ class Database
4
+ def self.connect
5
+ @db ||= SQLite3::Database.new(File.join(APP_ROOT, "db", "app.db"))
6
+ end
7
+
8
+ def self.execute_query(*query)
9
+ connect.execute(*query)
10
+ end
11
+ end
12
+ 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,5 @@
1
+ class Validations
2
+ def initialize
3
+
4
+ end
5
+ 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
@@ -0,0 +1,3 @@
1
+ module Vundabar
2
+ VERSION = "0.1.0".freeze
3
+ 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