panda_frwk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "panda"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,47 @@
1
+ require "panda/version"
2
+ require "panda/utils"
3
+ require "panda/record/base"
4
+ require "panda/routing/router"
5
+ require "panda/routing/mapper"
6
+ require "panda/base_controller"
7
+ require "panda/dependencies"
8
+
9
+ module Panda
10
+ class Application
11
+ attr_reader :routes
12
+
13
+ def initialize
14
+ @routes = Routing::Router.new
15
+ end
16
+
17
+ def call(env)
18
+ return [500, {}, []] if env["PATH_INFO"] == "/favicon.ico"
19
+ request = Rack::Request.new(env)
20
+ handler = mapper.perform(request)
21
+ if handler
22
+ call_controller_action(request, handler[:target])
23
+ else
24
+ process_invalid_request(request)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def call_controller_action(request, target)
31
+ controller = Object.const_get("#{target[0]}Controller")
32
+ controller.new(request).dispatch(target[1])
33
+ end
34
+
35
+ def process_invalid_request(request)
36
+ [
37
+ 404,
38
+ {},
39
+ ["Oops! No route for #{request.request_method} #{request.path_info}"]
40
+ ]
41
+ end
42
+
43
+ def mapper
44
+ @mapper ||= Routing::Mapper.new(routes.endpoints)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,68 @@
1
+ require "erb"
2
+ require "tilt"
3
+
4
+ module Panda
5
+ class BaseController
6
+ attr_reader :request
7
+
8
+ def initialize(request)
9
+ @request ||= request
10
+ end
11
+
12
+ def params
13
+ request.params
14
+ end
15
+
16
+ def redirect_to(location, status: 301)
17
+ response([], status, "Location" => location)
18
+ end
19
+
20
+ def response(body, status = 200, header = {})
21
+ @response = Rack::Response.new(body, status, header)
22
+ end
23
+
24
+ def get_response
25
+ @response
26
+ end
27
+
28
+ def render(*args)
29
+ response(render_template(*args))
30
+ end
31
+
32
+ def render_template(view_name, locals = {})
33
+ layout_template, view_template = layout_view_template(view_name)
34
+ title = view_name.to_s.tr("_", " ")
35
+ layout_template.render(self, title: title) do
36
+ view_template.render(self, locals)
37
+ end
38
+ end
39
+
40
+ def controller_name
41
+ self.class.to_s.gsub(/Controller$/, "").to_snake_case
42
+ end
43
+
44
+ def dispatch(action)
45
+ send(action)
46
+ render(action) unless get_response
47
+ get_response
48
+ end
49
+
50
+ private
51
+
52
+ def layout_view_template(view_name)
53
+ layout_template = Tilt::ERBTemplate.new(
54
+ File.join(APP_ROOT, "app", "views", "layouts", "application.html.erb")
55
+ )
56
+ view_template = Tilt::ERBTemplate.new(
57
+ File.join(
58
+ APP_ROOT,
59
+ "app",
60
+ "views",
61
+ controller_name,
62
+ "#{view_name}.html.erb"
63
+ )
64
+ )
65
+ [layout_template, view_template]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,6 @@
1
+ class Object
2
+ def self.const_missing(const)
3
+ require const.to_s.to_snake_case
4
+ Object.const_get(const)
5
+ end
6
+ end
@@ -0,0 +1,107 @@
1
+ require_relative "database"
2
+ require_relative "base_helper"
3
+
4
+ module Panda
5
+ module Record
6
+ class Base
7
+ include Record::BaseHelper
8
+
9
+ def initialize(attributes = {})
10
+ attributes.each { |column, value| send("#{column}=", value) }
11
+ end
12
+
13
+ def save
14
+ query = if id
15
+ "UPDATE #{model_table} SET " \
16
+ "#{update_placeholders} WHERE id = ?"
17
+ else
18
+ "INSERT INTO #{model_table} (#{current_table_columns})" \
19
+ " VALUES (#{current_table_placeholders})"
20
+ end
21
+ values = id ? record_values << id : record_values
22
+ Database.execute_query(query, values)
23
+ true
24
+ end
25
+
26
+ def update(attributes)
27
+ attributes.each do |key, value|
28
+ send("#{key}=", value)
29
+ end
30
+ save
31
+ end
32
+
33
+ alias save! save
34
+
35
+ def destroy
36
+ self.class.destroy(id)
37
+ end
38
+
39
+ def self.to_table(name)
40
+ @table = name.to_s
41
+ end
42
+
43
+ def self.property(column_name, constraints)
44
+ @properties ||= {}
45
+ @properties[column_name] = constraints
46
+ end
47
+
48
+ def self.create_table
49
+ Database.execute_query(
50
+ "CREATE TABLE IF NOT EXISTS #{table} " \
51
+ "(#{column_names_with_constraints.join(', ')})"
52
+ )
53
+ build_column_methods
54
+ end
55
+
56
+ def self.create(attributes)
57
+ model = new(attributes)
58
+ model.save
59
+ true
60
+ end
61
+
62
+ def self.all
63
+ Database.execute_query(
64
+ "SELECT * FROM #{table} ORDER BY id ASC"
65
+ ).map(&method(:get_model_object))
66
+ end
67
+
68
+ def self.find(id)
69
+ row = Database.execute_query(
70
+ "SELECT * FROM #{table} WHERE id = ?",
71
+ id.to_i
72
+ ).first
73
+ get_model_object(row)
74
+ end
75
+
76
+ def self.find_by(option)
77
+ row = Database.execute_query(
78
+ "SELECT * FROM #{table} WHERE #{option.keys.first} = ?",
79
+ option.values.first
80
+ ).first
81
+ get_model_object(row)
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
+ row = Database.execute_query(
87
+ "SELECT * FROM #{table} ORDER BY id " \
88
+ "#{method_name_and_order[1]} LIMIT 1"
89
+ ).first
90
+ get_model_object(row) unless row.nil?
91
+ end
92
+ end
93
+
94
+ def self.count
95
+ Database.execute_query("SELECT COUNT (*) FROM #{table}")[0][0]
96
+ end
97
+
98
+ def self.destroy(id)
99
+ Database.execute_query("DELETE FROM #{table} WHERE id = ?", id)
100
+ end
101
+
102
+ def self.destroy_all
103
+ Database.execute_query "DELETE FROM #{table}"
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,84 @@
1
+ module Panda
2
+ module Record
3
+ module BaseHelper
4
+ def self.included(base)
5
+ class << base
6
+ attr_reader :properties, :table
7
+
8
+ private
9
+
10
+ def column_names_with_constraints
11
+ name_with_constraints = []
12
+ properties.each do |column_name, constraints|
13
+ query_string = []
14
+ query_string << column_name.to_s
15
+ parse_constraints(constraints, query_string)
16
+ name_with_constraints << query_string.join(" ")
17
+ end
18
+ name_with_constraints
19
+ end
20
+
21
+ def parse_constraints(constraints, query_string)
22
+ constraints.each do |attribute, value|
23
+ query_string << send(attribute.to_s, value)
24
+ end
25
+ end
26
+
27
+ def build_column_methods
28
+ properties.keys.each(&method(:attr_accessor))
29
+ end
30
+
31
+ def get_model_object(row)
32
+ return nil unless row
33
+ model ||= new
34
+ properties.keys.each_with_index do |key, index|
35
+ model.send("#{key}=", row[index])
36
+ end
37
+ model
38
+ end
39
+
40
+ def type(value)
41
+ value.to_s
42
+ end
43
+
44
+ def primary_key(is_primary)
45
+ "PRIMARY KEY AUTOINCREMENT" if is_primary
46
+ end
47
+
48
+ def nullable(is_null)
49
+ "NOT NULL" unless is_null
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def model_table
57
+ self.class.table
58
+ end
59
+
60
+ def current_table_columns
61
+ columns_without_id.map(&:to_s).join(", ")
62
+ end
63
+
64
+ def current_table_placeholders
65
+ (["?"] * (self.class.properties.keys.size - 1)).join(", ")
66
+ end
67
+
68
+ def record_values
69
+ columns_without_id.map(&method(:send))
70
+ end
71
+
72
+ def update_placeholders(columns = self.class.properties.keys)
73
+ columns.delete(:id)
74
+ columns.map { |column| "#{column} = ?" }.join(", ")
75
+ end
76
+
77
+ def columns_without_id
78
+ columns ||= self.class.properties.keys
79
+ columns.delete(:id)
80
+ columns
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ require "sqlite3"
2
+
3
+ module Panda
4
+ module Record
5
+ class Database
6
+ def self.connection
7
+ @db ||= SQLite3::Database.new(File.join(APP_ROOT, "db", "app.db"))
8
+ end
9
+
10
+ def self.execute_query(*query)
11
+ connection.execute(*query)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ module Panda
2
+ module Routing
3
+ class Mapper
4
+ attr_reader :request, :endpoints
5
+
6
+ def initialize(endpoints)
7
+ @endpoints = endpoints
8
+ end
9
+
10
+ def perform(request)
11
+ @request = request
12
+ path = request.path_info
13
+ verb = request.request_method
14
+
15
+ endpoints[verb].detect do |endpoint|
16
+ match_path_with_endpoint(path, endpoint)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def match_path_with_endpoint(path, endpoint)
23
+ regex, placeholders = endpoint[:pattern]
24
+ if regex =~ path
25
+ match_data = $~
26
+ placeholders.each do |placeholder|
27
+ request.update_param(placeholder, match_data[placeholder])
28
+ end
29
+ return true
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,48 @@
1
+ module Panda
2
+ module Routing
3
+ class Router
4
+ attr_reader :endpoints
5
+
6
+ def initialize
7
+ @endpoints ||= Hash.new { |h, k| h[k] = [] }
8
+ end
9
+
10
+ def draw(&block)
11
+ instance_eval(&block)
12
+ end
13
+
14
+ %w(get post delete put patch).each do |method_name|
15
+ define_method(method_name) do |path, options|
16
+ route method_name.upcase, path, options
17
+ end
18
+ end
19
+
20
+ def root(target) get "/", to: target end
21
+
22
+ private
23
+
24
+ def route(verb, url, options = {})
25
+ url = "/#{url}" unless url[0] == "/"
26
+ @endpoints[verb] << {
27
+ pattern: match_placeholders(url),
28
+ path: Regexp.new("^#{url}$"),
29
+ target: set_controller_action(options[:to])
30
+ }
31
+ end
32
+
33
+ def match_placeholders(path)
34
+ placeholders = []
35
+ path_ = path.gsub(/(:\w+)/) do |match|
36
+ placeholders << match[1..-1].freeze
37
+ "(?<#{placeholders.last}>\\w+)"
38
+ end
39
+ [/^#{path_}$/, placeholders]
40
+ end
41
+
42
+ def set_controller_action(string)
43
+ string =~ /^([^#]+)#([^#]+)$/
44
+ [$1.to_camel_case, $2]
45
+ end
46
+ end
47
+ end
48
+ end