jun 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -15
- data/jun.gemspec +1 -1
- data/lib/jun/action_controller/base.rb +6 -10
- data/lib/jun/action_controller/callbacks.rb +46 -0
- data/lib/jun/action_controller/metal.rb +22 -0
- data/lib/jun/action_controller/redirecting.rb +17 -0
- data/lib/jun/action_controller/rendering.rb +0 -4
- data/lib/jun/action_dispatch/routing/mapper.rb +28 -3
- data/lib/jun/action_dispatch/routing/route_set.rb +48 -1
- data/lib/jun/action_dispatch/routing/welcome.html.erb +32 -0
- data/lib/jun/{active_record.rb → active_record/base.rb} +14 -3
- data/lib/jun/active_record/migration.rb +76 -0
- data/lib/jun/active_record/migrator.rb +80 -0
- data/lib/jun/active_record/persistence.rb +46 -0
- data/lib/jun/active_record/relation.rb +42 -0
- data/lib/jun/active_support/core_ext/array/conversion.rb +10 -0
- data/lib/jun/active_support/{inflector.rb → core_ext/string/inflector.rb} +0 -0
- data/lib/jun/active_support/core_ext.rb +5 -0
- data/lib/jun/application.rb +27 -0
- data/lib/jun/cli/commands/db/create.rb +21 -1
- data/lib/jun/cli/commands/db/drop.rb +4 -1
- data/lib/jun/cli/commands/db/migrate.rb +1 -1
- data/lib/jun/cli/commands/db/rollback.rb +15 -0
- data/lib/jun/cli/commands/db/schema/dump.rb +24 -0
- data/lib/jun/cli/commands/db/schema/load.rb +43 -0
- data/lib/jun/cli/commands/db/seed.rb +19 -0
- data/lib/jun/cli/commands/generate/migration.rb +27 -0
- data/lib/jun/cli/commands/new.rb +7 -2
- data/lib/jun/cli/commands/server.rb +1 -1
- data/lib/jun/cli/generator_templates/migration.rb.erb +11 -0
- data/lib/jun/cli/{generators/new → generator_templates/new_app}/Gemfile.erb +0 -0
- data/lib/jun/cli/{generators/new → generator_templates/new_app}/README.md.erb +0 -0
- data/lib/jun/cli/{generators/new → generator_templates/new_app}/app/controllers/application_controller.rb.erb +0 -0
- data/lib/jun/cli/{generators/new → generator_templates/new_app}/app/helpers/application_helper.rb.erb +0 -0
- data/lib/jun/cli/generator_templates/new_app/app/models/application_record.rb.erb +4 -0
- data/lib/jun/cli/{generators/new → generator_templates/new_app}/app/views/layouts/application.html.erb.erb +0 -0
- data/lib/jun/cli/generator_templates/new_app/bin/console.erb +8 -0
- data/lib/jun/cli/generator_templates/new_app/config/application.rb.erb +12 -0
- data/lib/jun/cli/generator_templates/new_app/config/environment.rb.erb +7 -0
- data/lib/jun/cli/{generators/new → generator_templates/new_app}/config/routes.rb.erb +0 -0
- data/lib/jun/cli/{generators/new → generator_templates/new_app}/config.ru.erb +1 -2
- data/lib/jun/cli/generator_templates/new_app/db/seeds.rb.erb +9 -0
- data/lib/jun/cli.rb +5 -0
- data/lib/jun/version.rb +1 -1
- data/lib/jun.rb +13 -2
- metadata +33 -14
- data/lib/jun/cli/generators/new/config/application.rb.erb +0 -18
- data/lib/jun/cli/generators/new/db/app.db.erb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ab6ae7fad2bb74ce92d8ef4a6c0b44c5016b04f02f6721d372694cb3b86c9a5
|
4
|
+
data.tar.gz: 5da3c8f0d65029c88022c5b8d37e00e3f92f20ed0cb7592975118eb2608bd11f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e99d2117bacaf90fd4400bf68725545ce2fd00f9a0292b22ba321cf3af195c1cb6900fcb0f2e66a98277351d7653c13e097270de1b85f8e2050b126e6c5c316b
|
7
|
+
data.tar.gz: e81734bc3e01a3682537a928f050c243c2ecb5ee90f15ba10262b872bc8a618d87ba4b4da9d34d108efa2c6d3b33e2230e4a9eed26f685c5d20bb5fcc287a7b8
|
data/README.md
CHANGED
@@ -1,32 +1,35 @@
|
|
1
1
|
# Jun
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/jun.svg)](https://badge.fury.io/rb/jun)
|
4
4
|
|
5
|
-
|
5
|
+
Jun is a simple, [Rails](https://github.com/rails/rails)-inspired web application framework. This is a rough implementation, built with the goal of learning more about Rails internals. Not meant for production use.
|
6
6
|
|
7
|
-
|
7
|
+
## Getting Started
|
8
8
|
|
9
|
-
|
10
|
-
gem 'jun'
|
11
|
-
```
|
9
|
+
Install the gem:
|
12
10
|
|
13
|
-
|
11
|
+
```
|
12
|
+
$ gem install jun
|
13
|
+
```
|
14
14
|
|
15
|
-
|
15
|
+
Then, create a new Jun application:
|
16
16
|
|
17
|
-
|
17
|
+
```
|
18
|
+
$ jun new my_app
|
19
|
+
```
|
18
20
|
|
19
|
-
|
21
|
+
Change directory into `my_app` and start up the server:
|
20
22
|
|
21
|
-
|
23
|
+
```
|
24
|
+
$ cd my_app
|
25
|
+
$ bin/jun server
|
26
|
+
```
|
22
27
|
|
23
|
-
|
28
|
+
Visit `http://localhost:6291` to view your app.
|
24
29
|
|
25
30
|
## Development
|
26
31
|
|
27
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can
|
28
|
-
|
29
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
33
|
|
31
34
|
## License
|
32
35
|
|
data/jun.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.email = ["zspesic@gmail.com"]
|
10
10
|
|
11
11
|
spec.summary = "A simple Ruby web framework."
|
12
|
-
spec.description = "A simple web framework inspired by Rails."
|
12
|
+
spec.description = "A simple web framework inspired by Rails. Not meant for production use."
|
13
13
|
spec.homepage = "https://github.com/zokioki/jun"
|
14
14
|
spec.license = "MIT"
|
15
15
|
spec.required_ruby_version = ">= 2.6.0"
|
@@ -1,20 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "metal"
|
4
|
+
require_relative "callbacks"
|
3
5
|
require_relative "rendering"
|
6
|
+
require_relative "redirecting"
|
4
7
|
|
5
8
|
module Jun
|
6
9
|
module ActionController
|
7
|
-
class Base
|
10
|
+
class Base < Metal
|
11
|
+
include Jun::ActionController::Callbacks
|
8
12
|
include Jun::ActionController::Rendering
|
9
|
-
|
10
|
-
attr_accessor :request, :response
|
11
|
-
|
12
|
-
def handle_response(action)
|
13
|
-
public_send(action)
|
14
|
-
render(action) unless response_rendered?
|
15
|
-
|
16
|
-
response
|
17
|
-
end
|
13
|
+
include Jun::ActionController::Redirecting
|
18
14
|
end
|
19
15
|
end
|
20
16
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module ActionController
|
5
|
+
module Callbacks
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle_response(action)
|
11
|
+
self.class.before_actions.each do |callback|
|
12
|
+
callback.call(self) if callback.match?(action)
|
13
|
+
end
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def before_action(method_name, options = {})
|
20
|
+
before_actions << Callback.new(method_name, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def before_actions
|
24
|
+
@before_actions ||= []
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Callback
|
29
|
+
def initialize(method_name, options)
|
30
|
+
@method_name = method_name
|
31
|
+
@options = options
|
32
|
+
end
|
33
|
+
|
34
|
+
def match?(action)
|
35
|
+
return true unless @options[:only]&.any?
|
36
|
+
|
37
|
+
@options[:only].include?(action.to_sym)
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(controller)
|
41
|
+
controller.send(@method_name)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module ActionController
|
5
|
+
class Metal
|
6
|
+
attr_accessor :request, :response
|
7
|
+
|
8
|
+
def handle_response(action)
|
9
|
+
public_send(action)
|
10
|
+
render(action) unless response_rendered?
|
11
|
+
|
12
|
+
response
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def response_rendered?
|
18
|
+
!!@_response_rendered
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module ActionController
|
5
|
+
module Redirecting
|
6
|
+
def redirect_to(location, options = {})
|
7
|
+
return if response_rendered?
|
8
|
+
|
9
|
+
response.location = location
|
10
|
+
response.status = options[:status] || 302
|
11
|
+
response.write("<html><body>You are being <a href=\"#{response.location}\">redirected</a>.</body></html>")
|
12
|
+
|
13
|
+
@_response_rendered = true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -9,10 +9,19 @@ module Jun
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def get(path, to:, as: nil)
|
12
|
-
|
13
|
-
|
12
|
+
add_route(:get, path, to: to, as: as)
|
13
|
+
end
|
14
|
+
|
15
|
+
def post(path, to:, as: nil)
|
16
|
+
add_route(:post, path, to: to, as: as)
|
17
|
+
end
|
18
|
+
|
19
|
+
def patch(path, to:, as: nil)
|
20
|
+
add_route(:patch, path, to: to, as: as)
|
21
|
+
end
|
14
22
|
|
15
|
-
|
23
|
+
def delete(path, to:, as: nil)
|
24
|
+
add_route(:delete, path, to: to, as: as)
|
16
25
|
end
|
17
26
|
|
18
27
|
def root(to:)
|
@@ -22,6 +31,22 @@ module Jun
|
|
22
31
|
def resources(plural_name)
|
23
32
|
get "/#{plural_name}", to: "#{plural_name}#index", as: plural_name.to_s
|
24
33
|
get "/#{plural_name}/new", to: "#{plural_name}#new", as: "new_#{plural_name.to_s.singularize}"
|
34
|
+
post "/#{plural_name}", to: "#{plural_name}#create"
|
35
|
+
get "/#{plural_name}/:id", to: "#{plural_name}#show", as: plural_name.to_s.singularize
|
36
|
+
get "/#{plural_name}/:id/edit", to: "#{plural_name}#edit", as: "edit_#{plural_name.to_s.singularize}"
|
37
|
+
patch "/#{plural_name}/:id", to: "#{plural_name}#update"
|
38
|
+
delete "/#{plural_name}/:id", to: "#{plural_name}#destroy"
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def add_route(method, path, to:, as: nil)
|
44
|
+
method = method.to_s.upcase
|
45
|
+
controller, action = to.split("#")
|
46
|
+
path = path.to_s.start_with?("/") ? path.to_s : "/#{path}"
|
47
|
+
as ||= path.sub("/", "")
|
48
|
+
|
49
|
+
@route_set.add_route(method, path, controller, action, as)
|
25
50
|
end
|
26
51
|
end
|
27
52
|
end
|
@@ -35,18 +35,21 @@ module Jun
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def call(env)
|
38
|
+
return welcome_response if @routes.none?
|
39
|
+
|
38
40
|
request = Rack::Request.new(env)
|
39
41
|
|
40
42
|
if route = find_route(request)
|
41
43
|
route.dispatch(request)
|
42
44
|
else
|
43
|
-
|
45
|
+
not_found_response
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
47
49
|
def add_route(*args)
|
48
50
|
route = Route.new(*args)
|
49
51
|
@routes.push(route)
|
52
|
+
define_url_helper(route)
|
50
53
|
|
51
54
|
route
|
52
55
|
end
|
@@ -59,6 +62,50 @@ module Jun
|
|
59
62
|
mapper = Jun::ActionDispatch::Routing::Mapper.new(self)
|
60
63
|
mapper.instance_eval(&block)
|
61
64
|
end
|
65
|
+
|
66
|
+
def url_helpers
|
67
|
+
@url_helpers ||= Module.new.extend(url_helpers_module).include(url_helpers_module)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def url_helpers_module
|
73
|
+
@url_helpers_module ||= Module.new
|
74
|
+
end
|
75
|
+
|
76
|
+
def define_url_helper(route)
|
77
|
+
path_name = "#{route.name}_path"
|
78
|
+
|
79
|
+
url_helpers_module.define_method(path_name) do |*args|
|
80
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
81
|
+
path = route.path
|
82
|
+
path_tokens = path.scan(/:\w+/)
|
83
|
+
|
84
|
+
path_tokens.each.with_index do |token, index|
|
85
|
+
path.sub!(token, args[index])
|
86
|
+
end
|
87
|
+
|
88
|
+
path
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def not_found_response
|
93
|
+
response = Rack::Response.new
|
94
|
+
response.content_type = "text/plain"
|
95
|
+
response.status = 404
|
96
|
+
response.write("Not found")
|
97
|
+
response.finish
|
98
|
+
end
|
99
|
+
|
100
|
+
def welcome_response
|
101
|
+
template_filepath = File.expand_path("welcome.html.erb", __dir__)
|
102
|
+
template = Tilt::ERBTemplate.new(template_filepath)
|
103
|
+
|
104
|
+
response = Rack::Response.new
|
105
|
+
response.content_type = "text/html"
|
106
|
+
response.write(template.render)
|
107
|
+
response.finish
|
108
|
+
end
|
62
109
|
end
|
63
110
|
end
|
64
111
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<title>Welcome to Jun</title>
|
5
|
+
<meta charset="utf-8">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
|
8
|
+
<style>
|
9
|
+
body {
|
10
|
+
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
|
11
|
+
background-color: #fff3f3;
|
12
|
+
color: #7d3737;
|
13
|
+
}
|
14
|
+
|
15
|
+
::selection {
|
16
|
+
background-color: #ffbdb6;
|
17
|
+
}
|
18
|
+
|
19
|
+
.root {
|
20
|
+
margin-top: 4rem;
|
21
|
+
text-align: center;
|
22
|
+
}
|
23
|
+
</style>
|
24
|
+
</head>
|
25
|
+
|
26
|
+
<body>
|
27
|
+
<div class="root">
|
28
|
+
<h1>Welcome to Jun</h1>
|
29
|
+
<small>Jun v<%= Jun::VERSION %> | Ruby v<%= RUBY_VERSION %> (<%= RUBY_PLATFORM %>)</small>
|
30
|
+
</div>
|
31
|
+
</body>
|
32
|
+
</html>
|
@@ -1,11 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "connection_adapters/sqlite_adapter"
|
3
|
+
require_relative "../connection_adapters/sqlite_adapter"
|
4
|
+
require_relative "./persistence"
|
4
5
|
|
5
6
|
module ActiveRecord
|
6
7
|
class Base
|
8
|
+
include ActiveRecord::Persistence
|
9
|
+
|
7
10
|
def initialize(attributes = {})
|
8
11
|
@attributes = attributes
|
12
|
+
@new_record = true
|
9
13
|
end
|
10
14
|
|
11
15
|
def method_missing(name, *args)
|
@@ -21,12 +25,19 @@ module ActiveRecord
|
|
21
25
|
end
|
22
26
|
|
23
27
|
def self.all
|
24
|
-
|
28
|
+
ActiveRecord::Relation.new(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.where(*args)
|
32
|
+
all.where(*args)
|
25
33
|
end
|
26
34
|
|
27
35
|
def self.find_by_sql(sql)
|
28
36
|
connection.execute(sql).map do |attributes|
|
29
|
-
new(attributes)
|
37
|
+
object = new(attributes)
|
38
|
+
object.instance_variable_set("@new_record", false)
|
39
|
+
|
40
|
+
object
|
30
41
|
end
|
31
42
|
end
|
32
43
|
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Migration
|
5
|
+
def up
|
6
|
+
raise NoMethodError, "Subclass must implement method."
|
7
|
+
end
|
8
|
+
|
9
|
+
def down
|
10
|
+
raise NoMethodError, "Subclass must implement method."
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_column(table_name, column_name, column_type, options = {})
|
14
|
+
sql = ["ALTER TABLE #{table_name} ADD COLUMN #{column_name}"]
|
15
|
+
|
16
|
+
sql << column_type.to_s.upcase
|
17
|
+
sql << "NOT NULL" if options[:null] == false
|
18
|
+
sql << "DEFAULT #{options[:default]}" if options[:default]
|
19
|
+
sql << "UNIQUE" if options[:unique] == true
|
20
|
+
|
21
|
+
execute(sql.join(" "))
|
22
|
+
end
|
23
|
+
|
24
|
+
def remove_column(table_name, column_name)
|
25
|
+
execute("ALTER TABLE #{table_name} DROP COLUMN #{column_name};")
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_table(table_name, options = {})
|
29
|
+
sql = "CREATE TABLE IF NOT EXISTS #{table_name}"
|
30
|
+
column_options = []
|
31
|
+
|
32
|
+
if options[:id] || !options.key?(:id)
|
33
|
+
column_options << {
|
34
|
+
name: :id,
|
35
|
+
type: :integer,
|
36
|
+
primary_key: true,
|
37
|
+
null: false
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
column_options += options.fetch(:columns, [])
|
42
|
+
|
43
|
+
columns_sql = column_options.map do |column|
|
44
|
+
next(column) if column.is_a?(String)
|
45
|
+
|
46
|
+
column_sql = []
|
47
|
+
|
48
|
+
column_sql << column[:name]
|
49
|
+
column_sql << column[:type]&.to_s&.upcase
|
50
|
+
column_sql << "PRIMARY KEY" if column[:primary_key] == true
|
51
|
+
column_sql << "NOT NULL" if column[:null] == false
|
52
|
+
column_sql << "DEFAULT #{column[:default]}" if column[:default]
|
53
|
+
column_sql << "UNIQUE" if column[:unique] == true
|
54
|
+
|
55
|
+
column_sql.compact.join(" ")
|
56
|
+
end
|
57
|
+
|
58
|
+
sql += " (#{columns_sql.join(", ")})"
|
59
|
+
sql += ";"
|
60
|
+
|
61
|
+
execute(sql)
|
62
|
+
end
|
63
|
+
|
64
|
+
def drop_table(table_name)
|
65
|
+
execute("DROP TABLE IF EXISTS #{table_name};")
|
66
|
+
end
|
67
|
+
|
68
|
+
def rename_table(old_table_name, new_table_name)
|
69
|
+
execute("ALTER TABLE #{old_table_name} RENAME TO #{new_table_name};")
|
70
|
+
end
|
71
|
+
|
72
|
+
def execute(*args)
|
73
|
+
ActiveRecord::Base.connection.execute(*args)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Migrator
|
5
|
+
ALLOWED_DIRECTIONS = %w[up down].freeze
|
6
|
+
|
7
|
+
def initialize(direction:)
|
8
|
+
unless ALLOWED_DIRECTIONS.include?(direction.to_s)
|
9
|
+
raise ArgumentError, "direction must be one of: #{ALLOWED_DIRECTIONS.inspect}"
|
10
|
+
end
|
11
|
+
|
12
|
+
@direction = direction.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
process_migrations!
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def process_migrations!
|
22
|
+
files_to_process = up_migration? ? pending_migration_files : processed_migration_files.last(1)
|
23
|
+
|
24
|
+
files_to_process.each do |filepath|
|
25
|
+
require filepath
|
26
|
+
|
27
|
+
filename = filepath.split("/").last.sub(".rb", "")
|
28
|
+
migration_version = filename.split("_").first
|
29
|
+
migration_class_name = filename.split("_", 2).last.camelize
|
30
|
+
migration_class = Object.const_get(migration_class_name)
|
31
|
+
|
32
|
+
migration_class.new.public_send(@direction)
|
33
|
+
up_migration? ? add_to_schema_migrations(migration_version) :
|
34
|
+
remove_from_schema_migrations(migration_version)
|
35
|
+
|
36
|
+
migration_verb = up_migration? ? "processed" : "rolled back"
|
37
|
+
puts "Migration #{migration_verb} (#{filename})."
|
38
|
+
end
|
39
|
+
|
40
|
+
dump_schema! if files_to_process.any?
|
41
|
+
end
|
42
|
+
|
43
|
+
def up_migration?
|
44
|
+
@direction == "up"
|
45
|
+
end
|
46
|
+
|
47
|
+
def pending_migration_files
|
48
|
+
@pending_migration_files ||= migration_files - processed_migration_files
|
49
|
+
end
|
50
|
+
|
51
|
+
def processed_migration_files
|
52
|
+
@processed_migration_files ||= migration_files.select do |filepath|
|
53
|
+
file_version = filepath.split("/").last.split("_").first
|
54
|
+
processed_versions.include?(file_version)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def migration_files
|
59
|
+
@migration_files ||= Dir.glob(Jun.root.join("db/migrate/*.rb")).sort
|
60
|
+
end
|
61
|
+
|
62
|
+
def processed_versions
|
63
|
+
ActiveRecord::Base.connection.execute("SELECT * FROM schema_migrations;").map do |attributes|
|
64
|
+
attributes[:version]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_to_schema_migrations(version)
|
69
|
+
ActiveRecord::Base.connection.execute("INSERT INTO schema_migrations (version) VALUES (#{version});")
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove_from_schema_migrations(version)
|
73
|
+
ActiveRecord::Base.connection.execute("DELETE FROM schema_migrations WHERE version = #{version};")
|
74
|
+
end
|
75
|
+
|
76
|
+
def dump_schema!
|
77
|
+
Jun::CLI.process_command(["db:schema:dump"])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Persistence
|
5
|
+
def self.included(klass)
|
6
|
+
klass.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def create(attributes = {})
|
11
|
+
object = new(attributes)
|
12
|
+
object.save
|
13
|
+
|
14
|
+
object
|
15
|
+
end
|
16
|
+
|
17
|
+
def primary_key=(value)
|
18
|
+
@primary_key = value.to_sym
|
19
|
+
end
|
20
|
+
|
21
|
+
def primary_key
|
22
|
+
defined?(@primary_key) ? @primary_key : :id
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def save
|
27
|
+
if new_record?
|
28
|
+
result = self.class.connection.execute("INSERT INTO #{self.class.table_name} (#{@attributes.keys.join(",")}) VALUES (#{@attributes.values.map { |v| "'#{v}'" }.join(",")}) RETURNING *;")
|
29
|
+
@attributes[self.class.primary_key] = result.first[self.class.primary_key]
|
30
|
+
@new_record = false
|
31
|
+
else
|
32
|
+
self.class.connection.execute("UPDATE #{self.class.table_name} SET #{@attributes.map { |k, v| "#{k} = #{v}" }.join(",")} WHERE id = #{id};")
|
33
|
+
end
|
34
|
+
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def new_record?
|
39
|
+
@new_record
|
40
|
+
end
|
41
|
+
|
42
|
+
def persisted?
|
43
|
+
!new_record?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Relation
|
5
|
+
def initialize(klass)
|
6
|
+
@klass = klass
|
7
|
+
@where_clauses = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def where!(condition)
|
11
|
+
clause = condition.is_a?(String) ? condition : condition.map { |k, v| "#{k} = #{v}" }.join(" AND ")
|
12
|
+
@where_clauses << clause
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def where(condition)
|
18
|
+
clone.where!(condition)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_sql
|
22
|
+
sql = "SELECT * FROM #{@klass.table_name}"
|
23
|
+
sql += " WHERE #{@where_clauses.join(" AND ")}" if @where_clauses.any?
|
24
|
+
|
25
|
+
sql
|
26
|
+
end
|
27
|
+
|
28
|
+
def records
|
29
|
+
@records ||= @klass.find_by_sql(to_sql)
|
30
|
+
end
|
31
|
+
|
32
|
+
alias to_a records
|
33
|
+
|
34
|
+
def first
|
35
|
+
records.first
|
36
|
+
end
|
37
|
+
|
38
|
+
def each(&block)
|
39
|
+
records.each(&block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
File without changes
|
data/lib/jun/application.rb
CHANGED
@@ -14,5 +14,32 @@ module Jun
|
|
14
14
|
def routes
|
15
15
|
@routes ||= Jun::ActionDispatch::Routing::RouteSet.new
|
16
16
|
end
|
17
|
+
|
18
|
+
def initialize!
|
19
|
+
return false if initialized? || Jun.application.nil?
|
20
|
+
|
21
|
+
# Add app/* directories to autoload paths.
|
22
|
+
Jun::ActiveSupport::Dependencies.autoload_paths += Jun.root.join("app").children
|
23
|
+
|
24
|
+
# Set up routes and make its helpers available to controllers & views.
|
25
|
+
require Jun.root.join("config/routes.rb")
|
26
|
+
url_helpers = Jun.application.routes.url_helpers
|
27
|
+
Jun::ActionController::Base.include(url_helpers)
|
28
|
+
Jun::ActionView::Base.include(url_helpers)
|
29
|
+
|
30
|
+
# Include all helpers in app/helpers directory.
|
31
|
+
Dir.glob(Jun.root.join("app/helpers/**/*.rb")).each do |filepath|
|
32
|
+
helper_class_name = File.basename(filepath, ".rb").camelize
|
33
|
+
helper_class = Object.const_get(helper_class_name)
|
34
|
+
|
35
|
+
Jun::ActionView::Base.include(helper_class)
|
36
|
+
end
|
37
|
+
|
38
|
+
@initialized = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialized?
|
42
|
+
!!@initialized
|
43
|
+
end
|
17
44
|
end
|
18
45
|
end
|
@@ -6,7 +6,27 @@ module Jun
|
|
6
6
|
module DB
|
7
7
|
class Create < Base
|
8
8
|
def process(*args)
|
9
|
-
|
9
|
+
db_filepath = Jun.root.join("db/app.db")
|
10
|
+
|
11
|
+
if File.exist?(db_filepath)
|
12
|
+
puts "Database already exists."
|
13
|
+
else
|
14
|
+
File.open(db_filepath, "w") {}
|
15
|
+
create_schema_migrations_table
|
16
|
+
puts "Created database in #{db_filepath}."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def create_schema_migrations_table
|
23
|
+
ActiveRecord::Base.connection.execute(
|
24
|
+
<<~SQL
|
25
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
26
|
+
version text NOT NULL PRIMARY KEY
|
27
|
+
);"
|
28
|
+
SQL
|
29
|
+
)
|
10
30
|
end
|
11
31
|
end
|
12
32
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
module DB
|
7
|
+
module Schema
|
8
|
+
class Dump < Base
|
9
|
+
def process(*args)
|
10
|
+
schema_filepath = Jun.root.join("db/schema.sql")
|
11
|
+
db_filepath = Jun.root.join("db/app.db")
|
12
|
+
|
13
|
+
Bundler.with_original_env do
|
14
|
+
system("bundle exec sqlite3 #{db_filepath} .schema > #{schema_filepath}")
|
15
|
+
end
|
16
|
+
|
17
|
+
puts "Database schema updated."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
module DB
|
7
|
+
module Schema
|
8
|
+
class Load < Base
|
9
|
+
def process(*args)
|
10
|
+
schema_filepath = Jun.root.join("db/schema.sql")
|
11
|
+
db_filepath = Jun.root.join("db/app.db")
|
12
|
+
|
13
|
+
Bundler.with_original_env do
|
14
|
+
system("bundle exec sqlite3 #{db_filepath} < #{schema_filepath}")
|
15
|
+
end
|
16
|
+
|
17
|
+
populate_schema_migrations!
|
18
|
+
|
19
|
+
puts "Database schema loaded."
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def populate_schema_migrations!
|
25
|
+
migration_files = Dir.glob(Jun.root.join("db/migrate/*.rb")).sort
|
26
|
+
|
27
|
+
migration_files.each do |filepath|
|
28
|
+
filename = filepath.split("/").last.sub(".rb", "")
|
29
|
+
migration_version = filename.split("_").first
|
30
|
+
|
31
|
+
add_to_schema_migrations(migration_version)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_to_schema_migrations(version)
|
36
|
+
ActiveRecord::Base.connection.execute("INSERT INTO schema_migrations (version) VALUES (#{version});")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
module DB
|
7
|
+
class Seed < Base
|
8
|
+
def process(*args)
|
9
|
+
seed_filepath = Jun.root.join("db/seed.rb")
|
10
|
+
abort("No seed file found.") unless File.exist?(seed_filepath)
|
11
|
+
|
12
|
+
load seed_filepath
|
13
|
+
puts "Seeding complete."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jun
|
4
|
+
module CLI
|
5
|
+
module Commands
|
6
|
+
module Generate
|
7
|
+
class Migration < Base
|
8
|
+
def process(*args)
|
9
|
+
migration_name = args.first
|
10
|
+
|
11
|
+
template_filepath = File.expand_path("../../generator_templates/migration.rb.erb", __dir__)
|
12
|
+
template = Tilt::ERBTemplate.new(template_filepath)
|
13
|
+
template_locals = { migration_name: migration_name }
|
14
|
+
|
15
|
+
filename = "#{Time.now.to_i}_#{migration_name}.rb"
|
16
|
+
filepath = Jun.root.join("db/migrate/#{filename}")
|
17
|
+
file_body = template.render(nil, template_locals)
|
18
|
+
|
19
|
+
filepath.dirname.mkpath
|
20
|
+
File.open(filepath, "w") { |f| f.write(file_body) }
|
21
|
+
puts "created #{filename}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/jun/cli/commands/new.rb
CHANGED
@@ -26,17 +26,20 @@ module Jun
|
|
26
26
|
"README.md",
|
27
27
|
"app/controllers/application_controller.rb",
|
28
28
|
"app/helpers/application_helper.rb",
|
29
|
+
"app/models/application_record.rb",
|
29
30
|
"app/views/layouts/application.html.erb",
|
31
|
+
"bin/console",
|
30
32
|
"config/application.rb",
|
33
|
+
"config/environment.rb",
|
31
34
|
"config/routes.rb",
|
32
|
-
"db/
|
35
|
+
"db/seeds.rb"
|
33
36
|
]
|
34
37
|
|
35
38
|
FileUtils.mkdir_p(app_name)
|
36
39
|
|
37
40
|
FileUtils.chdir(app_name) do
|
38
41
|
templates.each do |filepath|
|
39
|
-
template_filepath = File.expand_path("../
|
42
|
+
template_filepath = File.expand_path("../generator_templates/new_app/#{filepath}.erb", __dir__)
|
40
43
|
template = Tilt::ERBTemplate.new(template_filepath)
|
41
44
|
template_locals = { app_name: app_name }
|
42
45
|
file_body = template.render(nil, template_locals)
|
@@ -47,6 +50,8 @@ module Jun
|
|
47
50
|
puts "created #{filepath}"
|
48
51
|
end
|
49
52
|
|
53
|
+
FileUtils.chmod("u+x", "bin/console")
|
54
|
+
|
50
55
|
puts "Installing dependencies..."
|
51
56
|
Bundler.with_original_env { system("bundle install") }
|
52
57
|
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "jun"
|
4
|
+
|
5
|
+
# Require gems listed in the Gemfile, including any gems specified
|
6
|
+
# in the current environment group (e.g. :development, :production).
|
7
|
+
Bundler.require(*Jun.groups)
|
8
|
+
|
9
|
+
module <%= app_name.camelize %>
|
10
|
+
class Application < Jun::Application
|
11
|
+
end
|
12
|
+
end
|
File without changes
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is used by the `jun db:seed` command and should include
|
4
|
+
# any necessary logic for seeding the database.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# User.create(name: "Sam", occupation: "author")
|
9
|
+
# Post.create(title: "How to Build a Bird House")
|
data/lib/jun/cli.rb
CHANGED
@@ -11,7 +11,12 @@ module Jun
|
|
11
11
|
Jun::CLI::Commands::New,
|
12
12
|
Jun::CLI::Commands::DB::Create,
|
13
13
|
Jun::CLI::Commands::DB::Migrate,
|
14
|
+
Jun::CLI::Commands::DB::Rollback,
|
15
|
+
Jun::CLI::Commands::DB::Seed,
|
14
16
|
Jun::CLI::Commands::DB::Drop,
|
17
|
+
Jun::CLI::Commands::DB::Schema::Dump,
|
18
|
+
Jun::CLI::Commands::DB::Schema::Load,
|
19
|
+
Jun::CLI::Commands::Generate::Migration,
|
15
20
|
Jun::CLI::Commands::Server,
|
16
21
|
Jun::CLI::Commands::Version
|
17
22
|
].freeze
|
data/lib/jun/version.rb
CHANGED
data/lib/jun.rb
CHANGED
@@ -3,10 +3,13 @@
|
|
3
3
|
require "pathname"
|
4
4
|
|
5
5
|
require_relative "jun/version"
|
6
|
-
require_relative "jun/active_support/
|
6
|
+
require_relative "jun/active_support/core_ext"
|
7
7
|
require_relative "jun/active_support/dependencies"
|
8
8
|
require_relative "jun/action_dispatch/routing/route_set"
|
9
|
-
require_relative "jun/active_record"
|
9
|
+
require_relative "jun/active_record/base"
|
10
|
+
require_relative "jun/active_record/migration"
|
11
|
+
require_relative "jun/active_record/migrator"
|
12
|
+
require_relative "jun/active_record/relation"
|
10
13
|
require_relative "jun/action_controller/base"
|
11
14
|
require_relative "jun/application"
|
12
15
|
|
@@ -22,6 +25,14 @@ module Jun
|
|
22
25
|
project_root_path
|
23
26
|
end
|
24
27
|
|
28
|
+
def env
|
29
|
+
ENV["JUN_ENV"] || ENV["RACK_ENV"] || "development"
|
30
|
+
end
|
31
|
+
|
32
|
+
def groups
|
33
|
+
[:default, env]
|
34
|
+
end
|
35
|
+
|
25
36
|
private
|
26
37
|
|
27
38
|
def project_root_path
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jun
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zoran
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-03-
|
11
|
+
date: 2022-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -108,7 +108,7 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0.14'
|
111
|
-
description: A simple web framework inspired by Rails.
|
111
|
+
description: A simple web framework inspired by Rails. Not meant for production use.
|
112
112
|
email:
|
113
113
|
- zspesic@gmail.com
|
114
114
|
executables:
|
@@ -127,33 +127,52 @@ files:
|
|
127
127
|
- jun.gemspec
|
128
128
|
- lib/jun.rb
|
129
129
|
- lib/jun/action_controller/base.rb
|
130
|
+
- lib/jun/action_controller/callbacks.rb
|
131
|
+
- lib/jun/action_controller/metal.rb
|
132
|
+
- lib/jun/action_controller/redirecting.rb
|
130
133
|
- lib/jun/action_controller/rendering.rb
|
131
134
|
- lib/jun/action_dispatch/routing/mapper.rb
|
132
135
|
- lib/jun/action_dispatch/routing/route_set.rb
|
136
|
+
- lib/jun/action_dispatch/routing/welcome.html.erb
|
133
137
|
- lib/jun/action_view/base.rb
|
134
138
|
- lib/jun/action_view/helpers.rb
|
135
139
|
- lib/jun/action_view/helpers/url_helper.rb
|
136
|
-
- lib/jun/active_record.rb
|
140
|
+
- lib/jun/active_record/base.rb
|
141
|
+
- lib/jun/active_record/migration.rb
|
142
|
+
- lib/jun/active_record/migrator.rb
|
143
|
+
- lib/jun/active_record/persistence.rb
|
144
|
+
- lib/jun/active_record/relation.rb
|
145
|
+
- lib/jun/active_support/core_ext.rb
|
146
|
+
- lib/jun/active_support/core_ext/array/conversion.rb
|
147
|
+
- lib/jun/active_support/core_ext/string/inflector.rb
|
137
148
|
- lib/jun/active_support/dependencies.rb
|
138
|
-
- lib/jun/active_support/inflector.rb
|
139
149
|
- lib/jun/application.rb
|
140
150
|
- lib/jun/cli.rb
|
141
151
|
- lib/jun/cli/commands/base.rb
|
142
152
|
- lib/jun/cli/commands/db/create.rb
|
143
153
|
- lib/jun/cli/commands/db/drop.rb
|
144
154
|
- lib/jun/cli/commands/db/migrate.rb
|
155
|
+
- lib/jun/cli/commands/db/rollback.rb
|
156
|
+
- lib/jun/cli/commands/db/schema/dump.rb
|
157
|
+
- lib/jun/cli/commands/db/schema/load.rb
|
158
|
+
- lib/jun/cli/commands/db/seed.rb
|
159
|
+
- lib/jun/cli/commands/generate/migration.rb
|
145
160
|
- lib/jun/cli/commands/new.rb
|
146
161
|
- lib/jun/cli/commands/server.rb
|
147
162
|
- lib/jun/cli/commands/version.rb
|
148
|
-
- lib/jun/cli/
|
149
|
-
- lib/jun/cli/
|
150
|
-
- lib/jun/cli/
|
151
|
-
- lib/jun/cli/
|
152
|
-
- lib/jun/cli/
|
153
|
-
- lib/jun/cli/
|
154
|
-
- lib/jun/cli/
|
155
|
-
- lib/jun/cli/
|
156
|
-
- lib/jun/cli/
|
163
|
+
- lib/jun/cli/generator_templates/migration.rb.erb
|
164
|
+
- lib/jun/cli/generator_templates/new_app/Gemfile.erb
|
165
|
+
- lib/jun/cli/generator_templates/new_app/README.md.erb
|
166
|
+
- lib/jun/cli/generator_templates/new_app/app/controllers/application_controller.rb.erb
|
167
|
+
- lib/jun/cli/generator_templates/new_app/app/helpers/application_helper.rb.erb
|
168
|
+
- lib/jun/cli/generator_templates/new_app/app/models/application_record.rb.erb
|
169
|
+
- lib/jun/cli/generator_templates/new_app/app/views/layouts/application.html.erb.erb
|
170
|
+
- lib/jun/cli/generator_templates/new_app/bin/console.erb
|
171
|
+
- lib/jun/cli/generator_templates/new_app/config.ru.erb
|
172
|
+
- lib/jun/cli/generator_templates/new_app/config/application.rb.erb
|
173
|
+
- lib/jun/cli/generator_templates/new_app/config/environment.rb.erb
|
174
|
+
- lib/jun/cli/generator_templates/new_app/config/routes.rb.erb
|
175
|
+
- lib/jun/cli/generator_templates/new_app/db/seeds.rb.erb
|
157
176
|
- lib/jun/connection_adapters/sqlite_adapter.rb
|
158
177
|
- lib/jun/version.rb
|
159
178
|
homepage: https://github.com/zokioki/jun
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "jun"
|
4
|
-
|
5
|
-
Jun::ActiveSupport::Dependencies.autoload_paths += Jun.root.join("app").children
|
6
|
-
|
7
|
-
# Include all helpers in app/helpers directory
|
8
|
-
Dir.glob(Jun.root.join("app/helpers/**/*.rb")).each do |filepath|
|
9
|
-
helper_class_name = File.basename(filepath, ".rb").camelize
|
10
|
-
helper_class = Object.const_get(helper_class_name)
|
11
|
-
|
12
|
-
Jun::ActionView::Base.include(helper_class)
|
13
|
-
end
|
14
|
-
|
15
|
-
module <%= app_name.camelize %>
|
16
|
-
class Application < Jun::Application
|
17
|
-
end
|
18
|
-
end
|
File without changes
|