hemp 1.0.1

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
+
3
+ task default: :test
4
+ task :test do
5
+ Dir.glob("./test/*/*_test.rb").each { |file| require file }
6
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -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
@@ -0,0 +1,5 @@
1
+ require "hemp/version"
2
+ require "hemp/application"
3
+ require "hemp/controller"
4
+ require "hemp/base_record"
5
+ require "hemp/dependencies"
@@ -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,10 @@
1
+ class Hash
2
+ def slice(*args)
3
+ sliced_value = {}
4
+ args.each do |arg|
5
+ sliced_value[arg] = fetch(arg)
6
+ end
7
+
8
+ sliced_value
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ require "facets"
2
+
3
+ class Object
4
+ def self.const_missing(const_name)
5
+ require const_name.to_s.pathize
6
+ Object.const_get const_name
7
+ rescue LoadError
8
+ end
9
+ 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,10 @@
1
+ module Hemp
2
+ module Orm
3
+ class DatabaseError < StandardError
4
+ def initialize(custom_message = "")
5
+ super "Error while creating database. "\
6
+ "#{custom_message}"
7
+ end
8
+ end
9
+ end
10
+ 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