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.
@@ -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