reloj 0.1.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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +64 -0
  5. data/README.md +89 -0
  6. data/Rakefile +1 -0
  7. data/bin/reloj +66 -0
  8. data/lib/reloj/core/controller_base.rb +68 -0
  9. data/lib/reloj/core/flash.rb +38 -0
  10. data/lib/reloj/core/params.rb +52 -0
  11. data/lib/reloj/core/route_helper.rb +45 -0
  12. data/lib/reloj/core/router.rb +89 -0
  13. data/lib/reloj/core/session.rb +32 -0
  14. data/lib/reloj/orm/associatable.rb +70 -0
  15. data/lib/reloj/orm/associatable2.rb +34 -0
  16. data/lib/reloj/orm/db_connection.rb +59 -0
  17. data/lib/reloj/orm/model_base.rb +132 -0
  18. data/lib/reloj/orm/pg_db.rb +59 -0
  19. data/lib/reloj/orm/searchable.rb +26 -0
  20. data/lib/reloj/skeletons/common_files/Gemfile +6 -0
  21. data/lib/reloj/skeletons/common_files/Rakefile +25 -0
  22. data/lib/reloj/skeletons/new_app/app/controllers/.keep +0 -0
  23. data/lib/reloj/skeletons/new_app/app/models/.keep +0 -0
  24. data/lib/reloj/skeletons/new_app/app/views/.keep +0 -0
  25. data/lib/reloj/skeletons/new_app/config/routes.rb +7 -0
  26. data/lib/reloj/skeletons/new_app/db/.keep +0 -0
  27. data/lib/reloj/skeletons/sample_app/app/controllers/cats_controller.rb +24 -0
  28. data/lib/reloj/skeletons/sample_app/app/models/cat.rb +8 -0
  29. data/lib/reloj/skeletons/sample_app/app/views/cats_controller/index.html.erb +5 -0
  30. data/lib/reloj/skeletons/sample_app/app/views/cats_controller/new.html.erb +11 -0
  31. data/lib/reloj/skeletons/sample_app/config/routes.rb +10 -0
  32. data/lib/reloj/skeletons/sample_app/db/setup.sql +46 -0
  33. data/lib/reloj/version.rb +3 -0
  34. data/lib/reloj.rb +8 -0
  35. data/reloj.gemspec +41 -0
  36. metadata +206 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ee697fff25d8314ec1c3fd14e7316e34ffdd6e37
4
+ data.tar.gz: 9e0129f049be415f577a1f66a6bc55c18c9609fc
5
+ SHA512:
6
+ metadata.gz: 2d0a1fa8bffc06eb067d4fe9ebea366dbda41bfa50f367f62e9ba4b78af992279887c5787e8c956600c765a6cb33e4b7626e4e7b7eead06f08fd7cb1087fefef
7
+ data.tar.gz: f1d90f2e54f1e42d6337ccc7a780e130da1e161c2aab0ef109b5d4d10726a5a7f2b85b6d5c93325d58bf37db90ed63a049b087b42b46e6257a60f82ebe944d62
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,64 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ reloj (0.1.0)
5
+ activesupport (~> 4.2)
6
+ pg (~> 0.18)
7
+ require_all (~> 1.3)
8
+ webrick (~> 1.3)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activesupport (4.2.4)
14
+ i18n (~> 0.7)
15
+ json (~> 1.7, >= 1.7.7)
16
+ minitest (~> 5.1)
17
+ thread_safe (~> 0.3, >= 0.3.4)
18
+ tzinfo (~> 1.1)
19
+ byebug (6.0.2)
20
+ coderay (1.1.0)
21
+ diff-lcs (1.2.5)
22
+ i18n (0.7.0)
23
+ json (1.8.3)
24
+ method_source (0.8.2)
25
+ minitest (5.8.1)
26
+ pg (0.18.3)
27
+ pry (0.10.1)
28
+ coderay (~> 1.1.0)
29
+ method_source (~> 0.8.1)
30
+ slop (~> 3.4)
31
+ rake (10.4.2)
32
+ require_all (1.3.2)
33
+ rspec (3.3.0)
34
+ rspec-core (~> 3.3.0)
35
+ rspec-expectations (~> 3.3.0)
36
+ rspec-mocks (~> 3.3.0)
37
+ rspec-core (3.3.2)
38
+ rspec-support (~> 3.3.0)
39
+ rspec-expectations (3.3.1)
40
+ diff-lcs (>= 1.2.0, < 2.0)
41
+ rspec-support (~> 3.3.0)
42
+ rspec-mocks (3.3.2)
43
+ diff-lcs (>= 1.2.0, < 2.0)
44
+ rspec-support (~> 3.3.0)
45
+ rspec-support (3.3.0)
46
+ slop (3.6.0)
47
+ thread_safe (0.3.5)
48
+ tzinfo (1.2.2)
49
+ thread_safe (~> 0.1)
50
+ webrick (1.3.1)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ bundler (~> 1.10)
57
+ byebug (~> 6.0)
58
+ pry (~> 0.10)
59
+ rake (~> 10.0)
60
+ reloj!
61
+ rspec (~> 3.3)
62
+
63
+ BUNDLED WITH
64
+ 1.10.6
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Reloj
2
+ A lightweight web framework for Ruby for creating database-backed web applications with the Model-View-Controller pattern.
3
+
4
+ ## Getting Started
5
+
6
+ 1. Install Reloj at the command prompt if you haven't yet:
7
+
8
+ gem install reloj
9
+
10
+ 2. At the command prompt, create a new Reloj application:
11
+
12
+ reloj new myapp
13
+
14
+ where "myapp" is the application name.
15
+
16
+ 3. Change directory to `myapp` and start the web server:
17
+
18
+ cd myapp
19
+ reloj server
20
+
21
+
22
+ 4. Using a browser, go to `http://localhost:3000` and you'll see:
23
+ "Welcome to Reloj!"
24
+
25
+ ## Models and ORM
26
+ Reloj uses the active record pattern for its object-relational mapping.
27
+ To use this functionality in your app, create a class for your model in `app/models` and have the model inherit from ModelBase
28
+ ```ruby
29
+ class Cat < ModelBase
30
+ # custom code goes here
31
+
32
+ finalize!
33
+ end
34
+ ```
35
+ ###Some commands:
36
+
37
+ ```ruby
38
+ Cat.all
39
+ ```
40
+ * Returns an array with instances of class Cat, one instance for each row in table cats
41
+
42
+ ```ruby
43
+ Cat.find(2)
44
+ ```
45
+ * Finds the record in table cats with id 2, returns instance of Cat corresponding to that record
46
+
47
+ ## Controllers
48
+ Create controllers in `app/controllers`. Controllers should inherit from `ControllerBase`.
49
+
50
+ ```ruby
51
+ class CatsController < ControllerBase
52
+
53
+ def index
54
+ @cats = Cat.all
55
+ render :index
56
+ end
57
+
58
+ end
59
+ ```
60
+
61
+ ## Routes
62
+ Write your routes in `config/routes.rb`
63
+
64
+ ```ruby
65
+ module App
66
+ ROUTES = Proc.new do
67
+ get '/cats', CatsController, :index
68
+ get '/cats/new', CatsController, :new
69
+ post '/cats', CatsController, :create
70
+ end
71
+ end
72
+ ```
73
+
74
+ ## Running the sample app
75
+ Reloj includes a generator for a sample app. To check it out:
76
+
77
+ 1. Generate the sample app
78
+
79
+ reloj generate:sample
80
+
81
+ 2. Move into the sample app directory
82
+
83
+ cd reloj_sample
84
+
85
+ 3. Run the server
86
+
87
+ reloj server
88
+
89
+ 4. Navigate to localhost:3000
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/reloj ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'reloj'
4
+
5
+ class TaskHandler
6
+
7
+ def self.run_server
8
+ require 'require_all'
9
+ require_all "#{Dir.pwd}/app/**/*.rb"
10
+ require_relative "#{Dir.pwd}/config/routes.rb"
11
+
12
+ router = Reloj::Router.new
13
+ router.draw(&App::ROUTES)
14
+
15
+ server = WEBrick::HTTPServer.new(Port: 3000)
16
+ server.mount_proc('/') { |req, res| router.run(req, res) }
17
+ trap('INT') { server.shutdown }
18
+ server.start
19
+ end
20
+
21
+ def self.generate_project(src, name)
22
+ # copy skeleton
23
+ copy_directory(src, name)
24
+
25
+ # copy Rakefile and Gemfile
26
+ copy_directory("common_files/.", name)
27
+
28
+ # write db.yml
29
+ config = { dbname: name }
30
+ File.write(File.join(Dir.pwd, "#{name}/config/db.yml"), YAML.dump(config))
31
+ end
32
+
33
+ def self.copy_directory(src, dest)
34
+ FileUtils.cp_r(File.join(skels_dir, src), "./#{dest}")
35
+ end
36
+
37
+ def self.skels_dir
38
+ @skels_dir ||= File.expand_path(
39
+ File.dirname(__FILE__) +
40
+ '/../lib/reloj/skeletons'
41
+ )
42
+ end
43
+ end
44
+
45
+ case ARGV[0]
46
+ when "server"
47
+ TaskHandler.run_server
48
+ when "new"
49
+ name = ARGV[1].nil? ? "reloj_app" : ARGV[1]
50
+ TaskHandler.generate_project('new_app', name)
51
+ when "db"
52
+ case ARGV[1]
53
+ when "create"
54
+ Database.create
55
+ when "delete"
56
+ Database.delete
57
+ when "setup"
58
+ Database.setup
59
+ when "reset"
60
+ Database.reset
61
+ end
62
+ when "generate:sample"
63
+ TaskHandler.generate_project('sample_app', 'reloj_sample')
64
+ else
65
+ puts "Invalid reloj command"
66
+ end
@@ -0,0 +1,68 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
3
+ require 'active_support/inflector'
4
+ require 'erb'
5
+ require_relative './session'
6
+ require_relative './params'
7
+ require_relative './flash'
8
+ require_relative './route_helper'
9
+
10
+
11
+ module Reloj
12
+ class ControllerBase
13
+ include RouteHelper
14
+ attr_reader :params, :req, :res
15
+
16
+ def initialize(req, res, route_params = {}, paths = [])
17
+ @req = req
18
+ @res = res
19
+ @already_built_response = false
20
+ @params = Params.new(req, route_params)
21
+ @paths = paths
22
+ self.class.create_helper_methods(paths)
23
+ end
24
+
25
+ def already_built_response?
26
+ @already_built_response
27
+ end
28
+
29
+ def redirect_to(url)
30
+ raise if already_built_response?
31
+ @res.status = 302
32
+ @res["Location"] = url
33
+ @already_built_response = true
34
+ flash.store_flash(@res)
35
+ session.store_session(@res)
36
+ end
37
+
38
+ def render_content(content, content_type)
39
+ raise if already_built_response?
40
+ @already_built_response = true
41
+ @res.content_type = content_type
42
+ @res.body = content
43
+ flash.store_flash(@res)
44
+ session.store_session(@res)
45
+ end
46
+
47
+ def render(template_name)
48
+ controller_name = self.class.to_s.underscore
49
+ f = File.read("app/views/#{controller_name}/#{template_name}.html.erb")
50
+ f = ERB.new(f)
51
+ render_content(f.result(binding), "text/html")
52
+ end
53
+
54
+ def session
55
+ @session ||= Session.new(@req)
56
+ end
57
+
58
+ def flash
59
+ @flash ||= Flash.new(@req)
60
+ end
61
+
62
+ def invoke_action(name)
63
+ self.send(name)
64
+ render(name) unless self.already_built_response?
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,38 @@
1
+ require 'json'
2
+ require 'webrick'
3
+
4
+ module Reloj
5
+ class Flash
6
+ # find the cookie for this app
7
+ # deserialize the cookie into a hash
8
+ def initialize(req)
9
+ req.cookies.each do |cookie|
10
+ if cookie.name == '_flash'
11
+ @_old_flash = JSON.parse(cookie.value)
12
+ end
13
+ end
14
+ @_old_flash ||= {}
15
+ @_new_flash ||= {}
16
+ end
17
+
18
+ def [](key)
19
+ @_old_flash[key] || @_new_flash[key]
20
+ end
21
+
22
+ def []=(key, val)
23
+ @_new_flash[key] = val
24
+ end
25
+
26
+ def now
27
+ @_old_flash
28
+ end
29
+
30
+ # serialize the hash into json and save in a cookie
31
+ # add to the responses cookies
32
+ def store_flash(res)
33
+ cookie = WEBrick::Cookie.new('_flash', @_new_flash.to_json)
34
+ cookie.path = '/'
35
+ res.cookies << cookie
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,52 @@
1
+ require 'uri'
2
+
3
+ module Reloj
4
+ class Params
5
+ def initialize(req, route_params = {})
6
+ @params = route_params
7
+ if req.query_string
8
+ @params.merge!(parse_www_encoded_form(req.query_string))
9
+ end
10
+ @params.merge!(parse_www_encoded_form(req.body)) if req.body
11
+ end
12
+
13
+ def [](key)
14
+ key = key.to_s
15
+ @params[key]
16
+ end
17
+
18
+ def to_s
19
+ @params.to_json.to_s
20
+ end
21
+
22
+ class AttributeNotFoundError < ArgumentError; end;
23
+
24
+ private
25
+ # this should return deeply nested hash, ex:
26
+ # user[address][street]=main&user[address][zip]=89436
27
+ # should return
28
+ # { "user" => { "address" => { "street" => "main", "zip" => "89436" } } }
29
+ def parse_www_encoded_form(www_encoded_form)
30
+ x = URI::decode_www_form(www_encoded_form)
31
+ result = {}
32
+ x.each do |arr|
33
+ parsed_keys = parse_key(arr.first)
34
+ value = arr.last
35
+ location_in_result = result
36
+ parsed_keys.each_with_index do |parsed_key, i|
37
+ location_in_result[parsed_key] = value if i == parsed_keys.length - 1
38
+ location_in_result[parsed_key] ||= {}
39
+ location_in_result = location_in_result[parsed_key]
40
+ end
41
+ end
42
+
43
+ result
44
+ end
45
+
46
+ # this should return an array
47
+ # user[address][street] should return ['user', 'address', 'street']
48
+ def parse_key(key)
49
+ key.split(/\]\[|\[|\]/)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ module RouteHelper
2
+
3
+ def RouteHelper.included(klass)
4
+ klass.extend ClassMethods
5
+ end
6
+
7
+ #maybe add link_to and button_to here
8
+
9
+ module ClassMethods
10
+
11
+ def create_helper_methods(paths)
12
+ paths.each do |path|
13
+ create_helper_method(path)
14
+ end
15
+ end
16
+
17
+ def create_helper_method(path)
18
+ nouns = path.split('/')
19
+ nouns.delete("")
20
+ if nouns.any? { |noun| noun[0] == ":" }
21
+ build_nested_route_helper(nouns)
22
+ else
23
+ method_name = nouns.join("_") + "_path"
24
+ define_method(method_name) { path }
25
+ end
26
+ end
27
+
28
+ def build_nested_route_helper(nouns)
29
+ just_names = nouns.select { |noun| noun[0] != ":" }
30
+ method_name = nouns.select do |noun|
31
+ noun[0] != ":"
32
+ end.join("_") + "_path"
33
+
34
+ define_method(method_name) do |*ids|
35
+ result = []
36
+ just_names.each do |noun|
37
+ result << noun
38
+ result << ids.shift unless ids.empty?
39
+ end
40
+ result.join('/')
41
+ end
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,89 @@
1
+ require_relative './route_helper'
2
+
3
+
4
+ module Reloj
5
+ class Route
6
+ attr_reader :pattern, :http_method, :controller_class, :action_name
7
+
8
+ def initialize(pattern, http_method, controller_class, action_name)
9
+ @pattern = pattern
10
+ @http_method = http_method
11
+ @controller_class = controller_class
12
+ @action_name = action_name
13
+ # save all helpers in the routes
14
+ end
15
+
16
+
17
+ def matches?(req)
18
+ req.request_method.downcase.to_sym == @http_method &&
19
+ @pattern.match(req.path)
20
+ end
21
+
22
+
23
+ def run(req, res, paths = [])
24
+ route_params = {}
25
+ matches = @pattern.match(req.path)
26
+ matches.names.each do |key|
27
+ route_params[key] = matches[key]
28
+ end
29
+ #pass the methods to the controller class here
30
+ @controller_class.new(req, res, route_params, paths).invoke_action(@action_name)
31
+ end
32
+ end
33
+
34
+ class Router
35
+ attr_reader :routes, :helpers
36
+
37
+ def self.regex_from_pattern_string(pattern_string)
38
+ x = pattern_string.split('/').map do |part|
39
+ part.gsub(/:(.+)[\/]{,1}/, '(?<\1>\d+)')
40
+ end.join('/')
41
+
42
+ Regexp.new("^#{x}\/?$")
43
+ end
44
+
45
+ def initialize
46
+ @routes = []
47
+ @paths = []
48
+ end
49
+
50
+ def add_helper_attributes(pattern, action_name)
51
+ #need controller name
52
+ # will get that when passed to controller
53
+ #need action
54
+ @paths << pattern
55
+ end
56
+
57
+ # simply adds a new route to the list of routes
58
+ def add_route(pattern, method, controller_class, action_name)
59
+ parsed_pattern = Router.regex_from_pattern_string(pattern.to_s)
60
+ route = Route.new(parsed_pattern, method, controller_class, action_name)
61
+ @routes << route
62
+ add_helper_attributes(pattern, action_name)
63
+ end
64
+
65
+ def draw(&proc)
66
+ instance_eval(&proc)
67
+ end
68
+
69
+ [:get, :post, :put, :delete].each do |http_method|
70
+ define_method(http_method) do |pattern, controller_class, action_name|
71
+ add_route(pattern, http_method, controller_class, action_name)
72
+ end
73
+ end
74
+
75
+ def match(req)
76
+ @routes.each { |route| return route if route.matches?(req) }
77
+ nil
78
+ end
79
+
80
+ def run(req, res)
81
+ if match(req)
82
+ #pass in the helper_attributes here
83
+ match(req).run(req, res, @paths)
84
+ else
85
+ res.status = 404
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,32 @@
1
+ require 'json'
2
+ require 'webrick'
3
+
4
+ module Reloj
5
+ class Session
6
+ # find the cookie for this app, deserialize into hash
7
+ def initialize(req)
8
+ req.cookies.each do |cookie|
9
+ if cookie.name == '_rails_lite_app'
10
+ @_rails_lite_app = JSON.parse(cookie.value)
11
+ end
12
+ end
13
+ @_rails_lite_app ||= {}
14
+ end
15
+
16
+ # convenience methods
17
+ def [](key)
18
+ @_rails_lite_app[key]
19
+ end
20
+
21
+ def []=(key, val)
22
+ @_rails_lite_app[key] = val
23
+ end
24
+
25
+ # serialize the hash into json and save in a cookie, add to responses
26
+ def store_session(res)
27
+ cookie = WEBrick::Cookie.new('_rails_lite_app', @_rails_lite_app.to_json)
28
+ cookie.path = '/'
29
+ res.cookies << cookie
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,70 @@
1
+ require_relative 'model_base'
2
+ require 'active_support/inflector'
3
+
4
+ class AssocOptions
5
+ attr_accessor(
6
+ :foreign_key,
7
+ :class_name,
8
+ :primary_key
9
+ )
10
+
11
+ def model_class
12
+ class_name.constantize
13
+ end
14
+
15
+ def table_name
16
+ model_class.table_name
17
+ end
18
+ end
19
+
20
+ class BelongsToOptions < AssocOptions
21
+ def initialize(name, options = {})
22
+ defaults = {
23
+ foreign_key: "#{name.to_s.underscore}_id".to_sym,
24
+ primary_key: :id,
25
+ class_name: name.to_s.camelcase.singularize
26
+ }.merge(options)
27
+ defaults.each do |attribute, value|
28
+ self.send("#{attribute}=", value)
29
+ end
30
+ end
31
+ end
32
+
33
+ class HasManyOptions < AssocOptions
34
+ def initialize(name, self_class_name, options = {})
35
+ defaults = {
36
+ foreign_key: "#{self_class_name.to_s.underscore}_id".to_sym,
37
+ primary_key: :id,
38
+ class_name: name.to_s.singularize.camelcase
39
+ }.merge(options)
40
+ defaults.each do |attribute, value|
41
+ self.send("#{attribute}=", value)
42
+ end
43
+ end
44
+ end
45
+
46
+ module Associatable
47
+ def belongs_to(name, options = {})
48
+ assoc_options[name] = options = BelongsToOptions.new(name, options)
49
+ define_method(name) do
50
+ foreign_key_value = send(options.foreign_key)
51
+ model_class = options.model_class
52
+ model_class.where(options.primary_key => foreign_key_value).first
53
+ end
54
+ end
55
+
56
+ def has_many(name, options = {})
57
+ options = HasManyOptions.new(name, self.name, options)
58
+ define_method(name) do
59
+ options.model_class.where( options.foreign_key => id )
60
+ end
61
+ end
62
+
63
+ def assoc_options
64
+ @assoc_options ||= {}
65
+ end
66
+ end
67
+
68
+ class ModelBase
69
+ extend Associatable
70
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'associatable'
2
+
3
+ module Associatable
4
+ # Remember to go back to 04_associatable to write ::assoc_options
5
+
6
+ def has_one_through(name, through_name, source_name)
7
+ self_options = assoc_options[name]
8
+ through_options = assoc_options[through_name]
9
+ #very close to finishing
10
+ define_method(name) do
11
+ # p all your assoc_options keys/values
12
+ source_options = through_options.model_class.assoc_options[source_name]
13
+ source_table = source_options.table_name
14
+ through_table = through_options.table_name
15
+ x = DBConnection.execute(<<-SQL)
16
+ SELECT
17
+ #{source_table}.*
18
+ FROM
19
+ #{source_table}
20
+ INNER JOIN
21
+ #{through_table}
22
+ ON
23
+ #{source_table}.id = #{through_table}.#{source_options.foreign_key}
24
+ INNER JOIN
25
+ #{self.class.table_name}
26
+ ON
27
+ #{self.class.table_name}.#{through_options.foreign_key} = #{through_table}.id;
28
+ SQL
29
+
30
+ source_options.model_class.parse_all(x).first
31
+ # use constantize
32
+ end
33
+ end
34
+ end