panda_frwk 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,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