hemp 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.hound.yml +4 -0
- data/.rubocop.yml +233 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +256 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/hemp.gemspec +45 -0
- data/lib/hemp.rb +5 -0
- data/lib/hemp/aliases.rb +13 -0
- data/lib/hemp/application.rb +59 -0
- data/lib/hemp/base_record.rb +96 -0
- data/lib/hemp/controller.rb +54 -0
- data/lib/hemp/dependencies.rb +10 -0
- data/lib/hemp/extensions/hash.rb +10 -0
- data/lib/hemp/extensions/object.rb +9 -0
- data/lib/hemp/orm/class_instance_vars.rb +21 -0
- data/lib/hemp/orm/database_error.rb +10 -0
- data/lib/hemp/orm/property.rb +49 -0
- data/lib/hemp/orm/record_interface.rb +99 -0
- data/lib/hemp/orm/sql_helper.rb +14 -0
- data/lib/hemp/routing/base.rb +83 -0
- data/lib/hemp/routing/route.rb +53 -0
- data/lib/hemp/routing/route_extensions.rb +22 -0
- data/lib/hemp/routing/route_split.rb +37 -0
- data/lib/hemp/routing/route_syntax_error.rb +10 -0
- data/lib/hemp/version.rb +3 -0
- metadata +217 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "hemp"
|
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
data/hemp.gemspec
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "hemp/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hemp"
|
8
|
+
spec.version = Hemp::VERSION
|
9
|
+
spec.authors = ["Tobi Oduah"]
|
10
|
+
spec.email = ["tobi.oduah@andela.com"]
|
11
|
+
|
12
|
+
spec.summary = "Simple web framework for building web apps in Ruby "\
|
13
|
+
"following the MVC design pattern"
|
14
|
+
spec.description = "Simple web framework for building web apps in Ruby "\
|
15
|
+
"following the MVC design pattern"
|
16
|
+
spec.homepage = "https://github.com/andela-toduah/hemp-web-framework"
|
17
|
+
spec.license = "MIT"
|
18
|
+
|
19
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
20
|
+
# delete this section to allow pushing this gem to any host.
|
21
|
+
if spec.respond_to?(:metadata)
|
22
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
23
|
+
else
|
24
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem "\
|
25
|
+
"pushes."
|
26
|
+
end
|
27
|
+
|
28
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
29
|
+
f.match(%r{^(test|spec|features)/})
|
30
|
+
end
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
spec.add_dependency "bundler", "~> 1.10"
|
36
|
+
spec.add_dependency "rake", "~> 10.0"
|
37
|
+
spec.add_dependency "rack"
|
38
|
+
spec.add_dependency "rack-test"
|
39
|
+
spec.add_dependency "facets"
|
40
|
+
spec.add_dependency "minitest"
|
41
|
+
spec.add_dependency "erubis"
|
42
|
+
spec.add_dependency "tilt"
|
43
|
+
spec.add_dependency "sqlite3"
|
44
|
+
spec.add_dependency "codeclimate-test-reporter"
|
45
|
+
end
|
data/lib/hemp.rb
ADDED
data/lib/hemp/aliases.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "hemp/routing/route"
|
2
|
+
require "hemp/routing/route_syntax_error"
|
3
|
+
require "hemp/routing/route_extensions"
|
4
|
+
require "hemp/routing/route_split"
|
5
|
+
|
6
|
+
module Hemp
|
7
|
+
module Aliases
|
8
|
+
RouteAlias = Hemp::Routing::Route
|
9
|
+
RouteError = Hemp::Routing::RouteSyntaxError
|
10
|
+
RouteSplitter = Hemp::Routing::RouteSplitter
|
11
|
+
RouteExtensions = Hemp::Routing::RouteExtensions
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "hemp/extensions/object"
|
2
|
+
require "hemp/extensions/hash"
|
3
|
+
require "hemp/routing/base"
|
4
|
+
require "rack"
|
5
|
+
|
6
|
+
module Hemp
|
7
|
+
class Application
|
8
|
+
attr_reader :pot, :request, :params
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@pot = Hemp::Routing::Base.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
@request = Rack::Request.new(env)
|
16
|
+
route = find_route(request) unless env.empty?
|
17
|
+
route ? process_request(route) : send_default_response
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_route(request)
|
21
|
+
verb = request.request_method.downcase.to_sym
|
22
|
+
route = @pot.routes[verb].detect do|route_val|
|
23
|
+
route_val == request.path_info
|
24
|
+
end
|
25
|
+
|
26
|
+
route
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_request(route)
|
30
|
+
controller_class = Object.
|
31
|
+
const_get(route.controller_camel).new
|
32
|
+
set_request_and_params_as_instance route, controller_class
|
33
|
+
obtain_appropriate_response(controller_class, route)
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_params(route)
|
37
|
+
request.params.merge(route.get_url_vars(request.path_info))
|
38
|
+
rescue RuntimeError
|
39
|
+
{}.merge(route.get_url_vars(request.path_info))
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_request_and_params_as_instance(route, controller_class)
|
43
|
+
params = get_params(route)
|
44
|
+
request.instance_variable_set "@params", params
|
45
|
+
controller_class.instance_variable_set "@request", request
|
46
|
+
end
|
47
|
+
|
48
|
+
def obtain_appropriate_response(controller_class, route)
|
49
|
+
controller_class.send(route.action_sym)
|
50
|
+
response = controller_class.response
|
51
|
+
|
52
|
+
response ? response : controller_class.render(route.action_sym)
|
53
|
+
end
|
54
|
+
|
55
|
+
def send_default_response
|
56
|
+
Rack::Response.new "Hello from Hemp", 200, {}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "sqlite3"
|
2
|
+
require "hemp/orm/database_error"
|
3
|
+
require "hemp/orm/property"
|
4
|
+
require "hemp/orm/sql_helper"
|
5
|
+
require "hemp/orm/record_interface"
|
6
|
+
require "hemp/orm/class_instance_vars"
|
7
|
+
|
8
|
+
module Hemp
|
9
|
+
class BaseRecord < Hemp::Orm::RecordInterface
|
10
|
+
include Hemp::Orm::ClassInstanceVars
|
11
|
+
|
12
|
+
def initialize(hash_arg = {})
|
13
|
+
self.class.expose_instance_vars
|
14
|
+
return super() if hash_arg.empty?
|
15
|
+
save_model_attributes hash_arg
|
16
|
+
end
|
17
|
+
|
18
|
+
def save_model_attributes(hash_arg)
|
19
|
+
row = []
|
20
|
+
properties.map do |property|
|
21
|
+
row << hash_arg.values_at(property.name)
|
22
|
+
end
|
23
|
+
|
24
|
+
self.class.set_instance_vars(self, row.flatten)
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
row = self.class.find id
|
29
|
+
return update if row
|
30
|
+
SqlHelper.execute get_save_query
|
31
|
+
set_id_value
|
32
|
+
rescue SQLite3::ConstraintException
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_id_value
|
37
|
+
id = SqlHelper.execute("select last_insert_rowid() "\
|
38
|
+
"from #{table_name}").first.first
|
39
|
+
instance_variable_set "@id", id
|
40
|
+
end
|
41
|
+
|
42
|
+
def update
|
43
|
+
SqlHelper.execute get_update_query
|
44
|
+
rescue SQLite3::ConstraintException
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def destroy
|
49
|
+
self.class.destroy id
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_save_query
|
53
|
+
sql_values = stringify_values.join(", ")
|
54
|
+
|
55
|
+
"insert into #{table_name} (#{sql_properties})"\
|
56
|
+
" values (#{sql_values})"
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_update_query
|
60
|
+
sql_update_properties = get_update_values.join(", ")
|
61
|
+
|
62
|
+
"update #{table_name} set #{sql_update_properties}"\
|
63
|
+
" where id = #{id}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_values
|
67
|
+
values = []
|
68
|
+
internal_props.each do |column|
|
69
|
+
values << (instance_variable_get("@#{column}") || "NULL")
|
70
|
+
end
|
71
|
+
|
72
|
+
values
|
73
|
+
end
|
74
|
+
|
75
|
+
def stringify_values
|
76
|
+
get_values.map do |val|
|
77
|
+
(val == "NULL") || (val == "") ? "NULL" : %('#{val}')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_update_values
|
82
|
+
all_update_properties = []
|
83
|
+
stringify_values.each_with_index do |value, index|
|
84
|
+
all_update_properties << (internal_props[index].to_s + " = " + value)
|
85
|
+
end
|
86
|
+
|
87
|
+
all_update_properties
|
88
|
+
end
|
89
|
+
|
90
|
+
def ==(other)
|
91
|
+
instance_variables.each do |var|
|
92
|
+
instance_variable_get(var) == other.instance_variable_get(var)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "tilt/erubis"
|
2
|
+
require "tilt"
|
3
|
+
require "facets"
|
4
|
+
require "ostruct"
|
5
|
+
require "rack"
|
6
|
+
|
7
|
+
module Hemp
|
8
|
+
class BaseController
|
9
|
+
attr_reader :response, :request
|
10
|
+
|
11
|
+
def render(action, instance_vars = get_instance_vars)
|
12
|
+
controller_folder = self.class.name.pathize.split("_").first
|
13
|
+
tilt_template = get_tilt_template(controller_folder, action)
|
14
|
+
instance_vars.delete_field :request
|
15
|
+
set_response(tilt_template.render(instance_vars))
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_tilt_template(controller_folder, action)
|
19
|
+
template_file = File.join(
|
20
|
+
RACK_ROOT, "app", "views", controller_folder, "#{action}.html.erb"
|
21
|
+
)
|
22
|
+
|
23
|
+
Tilt.new template_file
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_instance_vars
|
27
|
+
vars = instance_variables
|
28
|
+
instance_vars = {}
|
29
|
+
|
30
|
+
OpenStruct.new(pile_up_instance_vars(vars, instance_vars))
|
31
|
+
end
|
32
|
+
|
33
|
+
def pile_up_instance_vars(vars, instance_vars)
|
34
|
+
vars.each do |var|
|
35
|
+
trimmed_var = var[1..-1]
|
36
|
+
instance_vars[trimmed_var] = instance_variable_get "@#{trimmed_var}"
|
37
|
+
end
|
38
|
+
|
39
|
+
instance_vars
|
40
|
+
end
|
41
|
+
|
42
|
+
def redirect_to(location)
|
43
|
+
set_response [], 302, "Location" => location
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_response(body, status = 200, headers = {})
|
47
|
+
@response = Rack::Response.new(body, status, headers)
|
48
|
+
end
|
49
|
+
|
50
|
+
def params
|
51
|
+
@params ||= OpenStruct.new(request.params)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Hemp
|
2
|
+
module Dependencies
|
3
|
+
def self.load_files
|
4
|
+
$LOAD_PATH.unshift(File.join(RACK_ROOT, "app", "controllers"))
|
5
|
+
$LOAD_PATH.unshift(File.join(RACK_ROOT, "app", "models"))
|
6
|
+
Dir["#{RACK_ROOT}/app/controllers/*"].each { |file| load file }
|
7
|
+
Dir["#{RACK_ROOT}/app/models/*"].each { |file| load file }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Hemp
|
2
|
+
module Orm
|
3
|
+
module ClassInstanceVars
|
4
|
+
def properties
|
5
|
+
self.class.properties
|
6
|
+
end
|
7
|
+
|
8
|
+
def internal_props
|
9
|
+
self.class.internal_props
|
10
|
+
end
|
11
|
+
|
12
|
+
def sql_properties
|
13
|
+
self.class.sql_properties
|
14
|
+
end
|
15
|
+
|
16
|
+
def table_name
|
17
|
+
self.class.table_name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Hemp
|
2
|
+
module Orm
|
3
|
+
class Property
|
4
|
+
attr_reader :name, :options, :type, :primary_key, :nullable
|
5
|
+
def initialize(name, options = {})
|
6
|
+
@name = name
|
7
|
+
@options = options
|
8
|
+
parse_options
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse_options
|
12
|
+
set_default_options
|
13
|
+
@options.each_pair do |key, value|
|
14
|
+
case key
|
15
|
+
when :type
|
16
|
+
instance_variable_set("@type", value) if supported_type? value
|
17
|
+
when :primary_key, :nullable
|
18
|
+
instance_variable_set "@#{key}", value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_default_options
|
24
|
+
@type = "text"
|
25
|
+
@primary_key = false
|
26
|
+
@nullable = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{name} #{type}" <<
|
31
|
+
(nullable ? "" : " not null") <<
|
32
|
+
(primary_key ? " primary key" : "") <<
|
33
|
+
(primary_key && type == :integer ? " autoincrement" : "")
|
34
|
+
end
|
35
|
+
|
36
|
+
def supported_type?(arg)
|
37
|
+
if %w(boolean integer text).include? arg.to_s
|
38
|
+
true
|
39
|
+
else
|
40
|
+
raise DatabaseError.new "Type parameter passed in not supported."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
name == other.name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Hemp
|
2
|
+
module Orm
|
3
|
+
class RecordInterface
|
4
|
+
SqlHelper = Hemp::Orm::SqlHelper
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :table_name, :properties,
|
8
|
+
:internal_props, :sql_properties
|
9
|
+
|
10
|
+
def to_table(name)
|
11
|
+
@table_name = name
|
12
|
+
property :id, type: :integer, primary_key: true
|
13
|
+
end
|
14
|
+
|
15
|
+
def create(hash_arg)
|
16
|
+
new_model = const_get(name).new(hash_arg)
|
17
|
+
|
18
|
+
new_model.save ? new_model : false
|
19
|
+
end
|
20
|
+
|
21
|
+
def property(name, options)
|
22
|
+
@properties ||= []
|
23
|
+
new_property = Property.new(name, options)
|
24
|
+
@properties << new_property unless @properties.include? new_property
|
25
|
+
rescue DatabaseError => e
|
26
|
+
puts e.message
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_properties
|
31
|
+
props = @properties.map(&:name)
|
32
|
+
props.delete :id
|
33
|
+
|
34
|
+
props
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_table
|
38
|
+
SqlHelper.execute "create table if not exists "\
|
39
|
+
"#{@table_name} (#{@properties.join(', ')})"
|
40
|
+
|
41
|
+
construct_sql_properties
|
42
|
+
expose_instance_vars
|
43
|
+
end
|
44
|
+
|
45
|
+
def construct_sql_properties
|
46
|
+
@internal_props = get_properties
|
47
|
+
@sql_properties = @internal_props.join(", ")
|
48
|
+
end
|
49
|
+
|
50
|
+
def expose_instance_vars
|
51
|
+
@properties.map(&:name).each do |name|
|
52
|
+
const_get(self.name).class_eval { attr_accessor name.to_sym }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_instance_vars(scope, row)
|
57
|
+
@properties.map(&:name).each_with_index do |name, index|
|
58
|
+
scope.instance_variable_set "@#{name}", row[index]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize_model(row)
|
63
|
+
model = const_get(name).new
|
64
|
+
set_instance_vars model, row
|
65
|
+
|
66
|
+
model
|
67
|
+
end
|
68
|
+
|
69
|
+
def find(id)
|
70
|
+
row = SqlHelper.execute "select * from #{@table_name} "\
|
71
|
+
"where id = ?", [id]
|
72
|
+
|
73
|
+
return initialize_model(row.first) unless row.empty?
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def count
|
78
|
+
row = SqlHelper.execute "select count(*) from #{@table_name}"
|
79
|
+
|
80
|
+
row.first.first
|
81
|
+
end
|
82
|
+
|
83
|
+
def all
|
84
|
+
rows = SqlHelper.execute "select * from #{@table_name}"
|
85
|
+
rows.map { |row| initialize_model row }
|
86
|
+
end
|
87
|
+
|
88
|
+
def destroy(id)
|
89
|
+
SqlHelper.execute "delete from #{@table_name} "\
|
90
|
+
"where id = ?", [id]
|
91
|
+
end
|
92
|
+
|
93
|
+
def destroy_all
|
94
|
+
SqlHelper.execute "delete from #{@table_name}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|