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.
@@ -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