hemp 1.0.1
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.
- 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
|