algernon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "algernon"
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
data/bin/setup ADDED
@@ -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
Binary file
@@ -0,0 +1,53 @@
1
+ require "rack"
2
+ require "utility/request_handler"
3
+ require "utility/method_override"
4
+
5
+ module Algernon
6
+ class Application
7
+ attr_reader :request, :verb, :path, :router
8
+
9
+ alias_method :routes, :router
10
+
11
+ def initialize
12
+ @router = Routing::Router.new
13
+ end
14
+
15
+ def call(env)
16
+ env = MethodOverride.apply_to(env)
17
+ @request = Rack::Request.new(env)
18
+ if router.has_routes?
19
+ respond_to_request
20
+ else
21
+ default_response
22
+ end
23
+ end
24
+
25
+ def respond_to_request
26
+ route = router.get_match(request.request_method, request.path_info)
27
+ if route
28
+ handler = RequestHandler.new(request, route)
29
+ handler.response
30
+ else
31
+ page_not_found
32
+ end
33
+ end
34
+
35
+ def default_response
36
+ Rack::Response.new(
37
+ "<center><b>Algernon::Penny wise, Pound foolish</center>",
38
+ 200,
39
+ "Content-Type" => "text/html"
40
+ )
41
+ end
42
+
43
+ def page_not_found
44
+ Rack::Response.new(
45
+ "<center><h1>404 Error</h1>Page not found</center>",
46
+ 404,
47
+ "Content-Type" => "text/html"
48
+ )
49
+ end
50
+
51
+ private :respond_to_request, :default_response, :page_not_found
52
+ end
53
+ end
@@ -0,0 +1,54 @@
1
+ require "erubis"
2
+ require "active_support/core_ext/hash/indifferent_access"
3
+
4
+ module Algernon
5
+ class BaseController
6
+ attr_reader :request
7
+ def initialize(request)
8
+ @request = request
9
+ end
10
+
11
+ def get_response
12
+ @response
13
+ end
14
+
15
+ def render(view_name, locals = {})
16
+ filename = File.join(APP_ROOT,
17
+ "app",
18
+ "views",
19
+ controller_for_views,
20
+ "#{view_name}.html.erb"
21
+ )
22
+ template = File.read(filename)
23
+ parameters = process_view_variables(locals)
24
+ res_body = Erubis::Eruby.new(template).result(parameters)
25
+ response_setter(res_body)
26
+ end
27
+
28
+ def process_view_variables(locals)
29
+ hash = {}
30
+ vars = instance_variables
31
+ vars.each { |name| hash[name] = instance_variable_get(name) }
32
+
33
+ hash.merge(locals)
34
+ end
35
+
36
+ def response_setter(body, status = 200, headers = {})
37
+ @response = Rack::Response.new(body, status, headers)
38
+ end
39
+
40
+ def redirect_to(destination)
41
+ response_setter([], 302, "Location" => destination)
42
+ end
43
+
44
+ def params
45
+ request.params.with_indifferent_access
46
+ end
47
+
48
+ def controller_for_views
49
+ controller_class = self.class
50
+ controller_class = controller_class.to_s.gsub(/Controller$/, "")
51
+ controller_class.snakify
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,7 @@
1
+ class Object
2
+ def self.const_missing(c)
3
+ const = c.to_s
4
+ require c.to_s.snakify
5
+ const.constantify
6
+ end
7
+ end
@@ -0,0 +1,101 @@
1
+ require "sqlite3"
2
+ require "algernon/model/table_maker"
3
+ require "algernon/model/orm"
4
+
5
+ module Algernon
6
+ class Model
7
+ extend Algernon::TableMaker
8
+ extend Algernon::Orm
9
+
10
+ DB ||= SQLite3::Database.new(File.join("database", "data.sqlite"))
11
+
12
+ def initialize
13
+ end
14
+
15
+ def save
16
+ if id
17
+ query = "UPDATE #{self.class.table_name} "\
18
+ "SET #{update_field_set} "\
19
+ "WHERE id = ?"
20
+ DB.execute(query, table_values, id)
21
+ else
22
+ query = "INSERT INTO #{self.class.table_name} "\
23
+ "(#{table_fields}) "\
24
+ "VALUES(#{values_placeholders})"
25
+ DB.execute(query, table_values)
26
+
27
+ self.id = DB.execute("SELECT last_insert_rowid()")
28
+ end
29
+ self.class.find(id)
30
+ end
31
+
32
+ def update(parameters)
33
+ parameters.each do |key, value|
34
+ send("#{key}=", value)
35
+ end
36
+
37
+ save
38
+ end
39
+
40
+ def destroy
41
+ DB.execute("DELETE FROM #{self.class.table_name} WHERE id= ?", id)
42
+ end
43
+
44
+ def table_fields
45
+ columns_except_id.join(", ")
46
+ end
47
+
48
+ def columns_except_id
49
+ self.class.columns_array.without_id.with_value(self)
50
+ end
51
+
52
+ def values_placeholders
53
+ count = columns_except_id.count
54
+ (["?"] * count).join(", ")
55
+ end
56
+
57
+ def table_values
58
+ values = []
59
+ columns_except_id.each do |field|
60
+ values << send(field)
61
+ end
62
+ values
63
+ end
64
+
65
+ def update_field_set
66
+ values = []
67
+ columns_except_id.each do |field|
68
+ values << "#{field} = ? "
69
+ end
70
+ values.join(", ")
71
+ end
72
+
73
+ class << self
74
+ attr_reader :table_name, :fields
75
+
76
+ def to_table(table_name)
77
+ @table_name = table_name.to_s
78
+ @fields = {}
79
+ property :id, type: :integer, primary_key: true
80
+ end
81
+
82
+ def property(field_name, options = {})
83
+ @fields[field_name] = options
84
+ attr_accessor field_name
85
+ end
86
+
87
+ def create_table
88
+ query = "CREATE TABLE IF NOT EXISTS #{@table_name}"\
89
+ "(#{fields_builder(@fields)})"
90
+ DB.execute(query)
91
+ end
92
+
93
+ def columns_array
94
+ @columns_array ||= fields.keys
95
+ end
96
+ end
97
+
98
+ private :table_fields, :columns_except_id, :values_placeholders,
99
+ :table_values, :update_field_set
100
+ end
101
+ end
@@ -0,0 +1,51 @@
1
+ module Algernon
2
+ module Orm
3
+ DB ||= SQLite3::Database.new(File.join("database", "data.sqlite"))
4
+
5
+ def create(params)
6
+ model = new
7
+ params.each do |key, value|
8
+ model.send("#{key}=", value)
9
+ end
10
+
11
+ model.save
12
+ end
13
+
14
+ def all
15
+ fields = columns_array.join(", ")
16
+ data = DB.execute("SELECT id, #{fields} FROM #{table_name}")
17
+ data.map! do |row|
18
+ row_to_model(row)
19
+ end
20
+
21
+ data
22
+ end
23
+
24
+ def row_to_model(row)
25
+ model = new
26
+
27
+ columns_array.each_with_index do |field, index|
28
+ model.send("#{field}=", row[index + 1]) if row
29
+ end
30
+
31
+ model
32
+ end
33
+
34
+ def find(id)
35
+ fields = columns_array.join(", ")
36
+ row = DB.execute(
37
+ "SELECT id, #{fields} FROM #{table_name} WHERE id = ?",
38
+ id
39
+ ).first
40
+ row_to_model(row) if row
41
+ end
42
+
43
+ def destroy(id)
44
+ DB.execute("DELETE FROM #{table_name} WHERE id= ?", id)
45
+ end
46
+
47
+ def destroy_all
48
+ DB.execute("DELETE FROM #{table_name}")
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ module Algernon
2
+ module TableMaker
3
+ def fields_builder(fields)
4
+ columns_definition = ""
5
+ fields.each do |field, constraints|
6
+ columns_definition += field.to_s
7
+
8
+ constraints.each do |constraint_type, value|
9
+ columns_definition += " "
10
+ columns_definition += send(constraint_type, value)
11
+ end
12
+ columns_definition += ","
13
+ end
14
+ columns_definition[0..-2]
15
+ end
16
+
17
+ def primary_key(value)
18
+ return "PRIMARY KEY" if value
19
+ ""
20
+ end
21
+
22
+ def auto_increment(value)
23
+ "AUTOINCREMENT" if value
24
+ end
25
+
26
+ def type(value)
27
+ value.to_s.upcase
28
+ end
29
+
30
+ def nullable(value)
31
+ return "NOT NULL" unless value
32
+ "NULL"
33
+ end
34
+
35
+ def default(value)
36
+ "DEFAULT `#{value}`"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,44 @@
1
+ module Algernon
2
+ module Routing
3
+ class Finder
4
+ attr_reader :controller_name, :action, :path_regex, :url_placeholders
5
+ def initialize(path_regex, to, url_placeholders = {})
6
+ @controller_name, @action = get_controller_and_action(to)
7
+ @path_regex = path_regex
8
+ @url_placeholders = url_placeholders
9
+ end
10
+
11
+ def controller
12
+ Object.const_get(controller_name.camelify)
13
+ end
14
+
15
+ def get_url_parameters(actual_path)
16
+ parameters = {}
17
+ path = actual_path.split("/")
18
+ url_placeholders.each do |index, key|
19
+ parameters[key] = path[index.to_i]
20
+ end
21
+ parameters
22
+ end
23
+
24
+ def check_path(path)
25
+ (path_regex =~ path) == 0
26
+ end
27
+
28
+ def ==(other)
29
+ controller_name == other.controller_name &&
30
+ action == other.action &&
31
+ path_regex == other.path_regex &&
32
+ url_placeholders == other.url_placeholders
33
+ end
34
+
35
+ private
36
+
37
+ def get_controller_and_action(to)
38
+ controller, action = to.split("#")
39
+ controller_name = controller + "_controller"
40
+ [controller_name, action]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,31 @@
1
+ module Algernon
2
+ module Routing
3
+ module Route
4
+ def root(target)
5
+ get("/", to: target)
6
+ end
7
+
8
+ def resources(controller, options = {})
9
+ actions = detect_action(options)
10
+
11
+ # rubocop:disable Metrics/LineLength
12
+ get("/#{controller}", to: "#{controller}#index") if actions.include?(:index)
13
+ get("/#{controller}/new", to: "#{controller}#new") if actions.include?(:new)
14
+ post("/#{controller}", to: "#{controller}#create") if actions.include?(:create)
15
+ get("/#{controller}/:id", to: "#{controller}#show") if actions.include?(:show)
16
+ get("/#{controller}/:id/edit", to: "#{controller}#edit") if actions.include?(:edit)
17
+ put("/#{controller}/:id", to: "#{controller}#update") if actions.include?(:update)
18
+ patch("/#{controller}/:id", to: "#{controller}#update") if actions.include?(:update)
19
+ delete("/#{controller}/:id", to: "#{controller}#destroy") if actions.include?(:destroy)
20
+ # rubocop:enable Metrics/LineLength
21
+ end
22
+
23
+ def detect_action(options)
24
+ actions = [:index, :new, :create, :show, :edit, :update, :destroy]
25
+ actions -= options[:except] if options.key?(:except)
26
+ actions &= options[:only] if options.key?(:only)
27
+ actions
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,81 @@
1
+ require "algernon/routing/finder"
2
+ require "algernon/routing/route"
3
+
4
+ module Algernon
5
+ module Routing
6
+ class Router
7
+ include Algernon::Routing::Route
8
+ attr_accessor :routes, :url_placeholders, :part_regex
9
+
10
+ HTTP_VERBS = %w(get post put patch delete).freeze
11
+
12
+ def initialize
13
+ @routes = {}
14
+ @url_placeholders = {}
15
+ @part_regex = []
16
+ http_verb_creator
17
+ end
18
+
19
+ def draw(&block)
20
+ instance_eval(&block)
21
+ end
22
+
23
+ def has_routes?
24
+ true unless routes.empty?
25
+ end
26
+
27
+ def get_match(http_verb, path)
28
+ http_verb = http_verb.downcase
29
+ routes[http_verb].detect do |route|
30
+ route.check_path(path)
31
+ end
32
+ end
33
+
34
+ def http_verb_creator
35
+ self.class.class_eval do
36
+ HTTP_VERBS.each do |http_verb|
37
+ define_method(http_verb) do |path, to:|
38
+ process_and_store_route(http_verb, path, to)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def process_and_store_route(http_verb, path, to)
45
+ regex_parts, url_placeholders = extract_regex_and_placeholders(path)
46
+ path_regex = convert_regex_parts_to_path(regex_parts)
47
+ route_object = Algernon::Routing::Finder.new(
48
+ path_regex, to, url_placeholders
49
+ )
50
+ routes[http_verb.downcase.freeze] ||= []
51
+ routes[http_verb.downcase] << route_object
52
+ end
53
+
54
+ def extract_regex_and_placeholders(path)
55
+ path.path_format!
56
+
57
+ @part_regex = []
58
+ @url_placeholders = {}
59
+ path.split("/").each_with_index do |path_part, index|
60
+ store_part_and_placeholder(path_part, index)
61
+ end
62
+
63
+ [part_regex, url_placeholders]
64
+ end
65
+
66
+ def store_part_and_placeholder(path_part, index)
67
+ if path_part.start_with?(":")
68
+ url_placeholders[index] = path_part.delete(":").freeze
69
+ part_regex << "[a-zA-Z0-9_]+"
70
+ else
71
+ part_regex << path_part
72
+ end
73
+ end
74
+
75
+ def convert_regex_parts_to_path(regex_match)
76
+ regex_string = "^" + regex_match.join("/") + "/*$"
77
+ Regexp.new(regex_string)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ module Algernon
2
+ VERSION = "0.1.0".freeze
3
+ end
data/lib/algernon.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "algernon/version"
2
+ require "algernon/dependencies"
3
+ require "utility/string_utils"
4
+ require "algernon/routing/router"
5
+ require "algernon/routing/route"
6
+ require "algernon/routing/finder"
7
+ require "algernon/application"
8
+ require "algernon/controller/base_controller"
9
+ require "utility/array_utils"
10
+ require "algernon/model/model"
11
+ require "pry"
@@ -0,0 +1,9 @@
1
+ class Array
2
+ def without_id
3
+ reject { |key| key == :id }
4
+ end
5
+
6
+ def with_value(base)
7
+ reject { |field| base.send(field).nil? }
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require "rack"
2
+
3
+ class MethodOverride
4
+ def self.apply_to(env)
5
+ request = Rack::Request.new(env)
6
+ env["REQUEST_METHOD"] = request.params["_method"].
7
+ upcase if request.params["_method"]
8
+
9
+ env
10
+ end
11
+ end
@@ -0,0 +1,38 @@
1
+ module Algernon
2
+ class RequestHandler
3
+ attr_reader :request, :route, :response
4
+
5
+ def initialize(request, route)
6
+ @request = request
7
+ @route = route
8
+ process_request
9
+ end
10
+
11
+ private
12
+
13
+ def process_request
14
+ params = collage_parameters
15
+
16
+ request.instance_variable_set "@params", params
17
+
18
+ controller_constant = route.controller
19
+ controller_class = controller_constant.new(request)
20
+
21
+ @response = controller_response(controller_class, route.action.to_sym)
22
+ end
23
+
24
+ def collage_parameters
25
+ route_url_params = route.get_url_parameters(request.path_info)
26
+ request.params.merge(route_url_params)
27
+ end
28
+
29
+ def controller_response(controller, action)
30
+ controller.send(action)
31
+
32
+ unless controller.get_response
33
+ controller.render(action)
34
+ end
35
+ controller.get_response
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ class String
2
+ def snakify
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 camelify
12
+ return self if self !~ /_/ && self =~ /[A-Z]+.*/
13
+ split("_").map(&:capitalize).join
14
+ end
15
+
16
+ def constantify
17
+ Object.const_get(self)
18
+ end
19
+
20
+ def pluralize
21
+ gsub!(/([^aeiouy]|qu)y$/i, '\1ies')
22
+ gsub!(/(ss|z|ch|sh|x)$/i, '\1es')
23
+ gsub!(/(is)$/i, "es")
24
+ gsub!(/(f|fe)$/i, "ves")
25
+ gsub!(/(ex|ix)$/i, "ices")
26
+ gsub!(/(a)$/i, "ae")
27
+ gsub!(/(um|on)$/i, "a")
28
+ gsub!(/(us)$/i, "i")
29
+ gsub!(/(eau)$/i, "eaux")
30
+ gsub!(/([^saeix])$/i, '\1s')
31
+
32
+ self
33
+ end
34
+
35
+ def path_format!
36
+ replace(self[0..-2]) if self[-1] == "/"
37
+ replace("/" + self) if self[0] != "/"
38
+ end
39
+ end