reloj 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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