puffs 0.2.04 → 0.2.05
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.
- checksums.yaml +4 -4
- data/bin/puffs +39 -35
- data/lib/controller_base.rb +55 -55
- data/lib/db_connection.rb +94 -89
- data/lib/puffs.rb +5 -5
- data/lib/relation.rb +161 -159
- data/lib/router.rb +61 -57
- data/lib/server_connection.rb +15 -12
- data/lib/session.rb +28 -20
- data/lib/sql_object/associatable.rb +76 -74
- data/lib/sql_object/sql_object.rb +115 -114
- data/puffs.gemspec +21 -20
- data/readme.md +7 -0
- data/template/config/routes.rb +1 -1
- data/template/db/seeds.rb +23 -21
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c18567a1d51b75d3ad758258c55923663e43bd96
|
4
|
+
data.tar.gz: 35baf154d55ce813ad53d2b260c78a36b3925b14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e2c6e55a65a3aac467c51b21a93cc845489926d3c33186d6e311533bfdff2fc316ba9c2648a131127ff21cccbf76627b4c35dcbe2d977a20895272fa6be94c2
|
7
|
+
data.tar.gz: 6a7c885f4e2e91700482a70d8ebe6447f55fea35fb4ed36ee498281439dd1f2b42d1d9ff4d36a7b18a4d1ebc2091da80ab43355d637c3fe2d627edc06efc5c9e
|
data/bin/puffs
CHANGED
@@ -3,13 +3,14 @@
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'thor'
|
5
5
|
|
6
|
+
# Used to create models, controllers, and migrations. Alias 'g.'
|
6
7
|
class Generate < Thor
|
7
|
-
desc
|
8
|
+
desc 'model <name>', 'generate a model with the specified name.'
|
8
9
|
def model(name)
|
9
10
|
m_name = name.capitalize
|
10
11
|
|
11
|
-
#
|
12
|
-
File.open("./app/models/#{m_name.downcase}.rb",
|
12
|
+
# Writes model file
|
13
|
+
File.open("./app/models/#{m_name.downcase}.rb", 'w') do |f|
|
13
14
|
f.write("class #{m_name} < Puffs::SQLObject\n\n")
|
14
15
|
f.write("end\n")
|
15
16
|
f.write("#{m_name}.finalize!")
|
@@ -18,63 +19,65 @@ class Generate < Thor
|
|
18
19
|
puts "#{m_name} model created"
|
19
20
|
end
|
20
21
|
|
21
|
-
desc
|
22
|
+
desc 'controller <name>', 'generate a controller with the specified name.'
|
22
23
|
def controller(name)
|
23
24
|
c_name = name.capitalize
|
24
25
|
|
25
|
-
#Writes controller file
|
26
|
-
File.open("./app/controllers/#{c_name.downcase}.rb",
|
26
|
+
# Writes controller file
|
27
|
+
File.open("./app/controllers/#{c_name.downcase}.rb", 'w') do |f|
|
27
28
|
f.write("class #{c_name}Controller < Puffs::ControllerBase\n\n")
|
28
|
-
f.write(
|
29
|
+
f.write('end')
|
29
30
|
end
|
30
31
|
|
31
|
-
#
|
32
|
+
# Creates empty views directory
|
32
33
|
Dir.mkdir "./app/views/#{c_name.downcase}_controller"
|
33
34
|
puts "#{c_name} controller created"
|
34
35
|
end
|
35
36
|
|
36
|
-
desc
|
37
|
+
desc 'migration <name>', 'generates an empty sql file with a filename of the specified <name> appended to a timestamp'
|
37
38
|
def migration(name)
|
38
|
-
#
|
39
|
-
ts = Time.now.to_s.split(
|
39
|
+
# Create a timestamp
|
40
|
+
ts = Time.now.to_s.split(' ').take(2).join('').split('').map(&:to_i).join
|
40
41
|
require 'active_support/inflector'
|
41
42
|
filename = "#{ts}__#{name.underscore.downcase}"
|
42
43
|
|
43
|
-
#
|
44
|
-
File.open("./db/migrate/#{filename}.sql",
|
45
|
-
f.write
|
46
|
-
f.write
|
47
|
-
f.write
|
48
|
-
f.write
|
44
|
+
# Create the migration file
|
45
|
+
File.open("./db/migrate/#{filename}.sql", 'w') do |f|
|
46
|
+
f.write "CREATE TABLE IF NOT EXISTS #{name} (\n"
|
47
|
+
f.write "\tid SERIAL PRIMARY KEY,\n"
|
48
|
+
f.write "\tname VARCHAR(255) NOT NULL"
|
49
|
+
f.write ');'
|
49
50
|
end
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
54
|
+
# Top-level command for manipulating the database file.
|
53
55
|
class Db < Thor
|
54
|
-
desc
|
56
|
+
desc 'create', 'creates the DB'
|
55
57
|
def create
|
56
|
-
#Drops
|
58
|
+
# Drops puffs postgres database and recreates it.
|
57
59
|
require_relative '../lib/db_connection'
|
58
|
-
DBConnection.reset
|
60
|
+
Puffs::DBConnection.reset
|
59
61
|
puts 'db created!'
|
60
62
|
end
|
61
63
|
|
62
|
-
desc
|
64
|
+
desc 'migrate', 'runs pending migrations'
|
63
65
|
def migrate
|
64
|
-
#Creates Version table if necessary,
|
66
|
+
# Creates Version table if necessary,
|
67
|
+
# then runs needed migrations in order.
|
65
68
|
require_relative '../lib/db_connection'
|
66
|
-
DBConnection.migrate
|
67
|
-
puts
|
69
|
+
Puffs::DBConnection.migrate
|
70
|
+
puts 'migrated!'
|
68
71
|
end
|
69
72
|
|
70
|
-
desc
|
73
|
+
desc 'seed', 'seeds the DB'
|
71
74
|
def seed
|
72
75
|
require_relative '../lib/puffs'
|
73
|
-
Seed.populate
|
76
|
+
Puffs::Seed.populate
|
74
77
|
puts 'db seeded!'
|
75
78
|
end
|
76
79
|
|
77
|
-
desc
|
80
|
+
desc 'reset', 'resets the DB and seeds it'
|
78
81
|
def reset
|
79
82
|
create
|
80
83
|
migrate
|
@@ -83,17 +86,18 @@ class Db < Thor
|
|
83
86
|
end
|
84
87
|
end
|
85
88
|
|
89
|
+
# Top-level Thor class. Executes with 'puffs' after bundling.
|
86
90
|
class CLI < Thor
|
87
91
|
register(Generate, 'generate', 'generate <command>', 'Generates a model or controller.')
|
88
92
|
register(Db, 'db', 'db <command>', 'Accesses commands for the DB.')
|
89
93
|
|
90
|
-
desc
|
94
|
+
desc 'g', 'alias of generate subcommand'
|
91
95
|
subcommand 'g', Generate
|
92
96
|
|
93
97
|
desc 'server', 'starts the Puffs server'
|
94
98
|
def server
|
95
99
|
require_relative '../lib/puffs'
|
96
|
-
ServerConnection.start
|
100
|
+
Puffs::ServerConnection.start
|
97
101
|
end
|
98
102
|
|
99
103
|
desc 'new', 'creates a new Puffs app'
|
@@ -101,26 +105,26 @@ class CLI < Thor
|
|
101
105
|
Dir.mkdir "./#{name}"
|
102
106
|
Dir.mkdir "./#{name}/config"
|
103
107
|
|
104
|
-
File.open("./#{name}/config/database.yml",
|
108
|
+
File.open("./#{name}/config/database.yml", 'w') do |f|
|
105
109
|
f.write("database: #{name}")
|
106
110
|
end
|
107
|
-
|
111
|
+
|
108
112
|
Dir.mkdir "./#{name}/app"
|
109
113
|
Dir.mkdir "./#{name}/app/models"
|
110
114
|
Dir.mkdir "./#{name}/app/views"
|
111
115
|
Dir.mkdir "./#{name}/app/controllers"
|
112
|
-
File.open("./#{name}/app/controllers/application_controller.rb",
|
116
|
+
File.open("./#{name}/app/controllers/application_controller.rb", 'w') do |f|
|
113
117
|
f.write File.read(File.expand_path('../../template/app/controllers/application_controller.rb', __FILE__))
|
114
118
|
end
|
115
|
-
File.open("./#{name}/config/routes.rb",
|
119
|
+
File.open("./#{name}/config/routes.rb", 'w') do |f|
|
116
120
|
f.write File.read(File.expand_path('../../template/config/routes.rb', __FILE__))
|
117
121
|
end
|
118
122
|
Dir.mkdir "./#{name}/db"
|
119
123
|
Dir.mkdir "./#{name}/db/migrate"
|
120
|
-
File.open("./#{name}/db/seeds.rb",
|
124
|
+
File.open("./#{name}/db/seeds.rb", 'w') do |f|
|
121
125
|
f.write File.read(File.expand_path('../../template/db/seeds.rb', __FILE__))
|
122
126
|
end
|
123
|
-
File.open("./#{name}/Gemfile",
|
127
|
+
File.open("./#{name}/Gemfile", 'w') do |f|
|
124
128
|
f.write File.read(File.expand_path('../../template/Gemfile', __FILE__))
|
125
129
|
end
|
126
130
|
end
|
data/lib/controller_base.rb
CHANGED
@@ -3,73 +3,73 @@ require 'active_support/core_ext'
|
|
3
3
|
require 'erb'
|
4
4
|
require_relative 'session'
|
5
5
|
require 'active_support/inflector'
|
6
|
-
# require_relative 'puffs'
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
# Puffs controller
|
8
|
+
module Puffs
|
9
|
+
class ControllerBase
|
10
|
+
attr_reader :req, :res, :params
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
# Setup the controller
|
13
|
+
def initialize(req, res, route_params = {})
|
14
|
+
@req, @res = req, res
|
15
|
+
@params = req.params.merge(route_params)
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
# Helper method to alias @already_built_response
|
19
|
+
def already_built_response?
|
20
|
+
@already_built_response ||= false
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
# Set the response status code and header
|
24
|
+
def redirect_to(url)
|
25
|
+
raise DoubleRenderError if already_built_response?
|
26
|
+
res.header['location'] = url
|
27
|
+
res.status = 302
|
28
|
+
@already_built_response = true
|
29
|
+
session.store_session(res)
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
32
|
+
# Populate the response with content.
|
33
|
+
# Set the response's content type to the given type.
|
34
|
+
# Raise an error if the developer tries to double render.
|
35
|
+
def render_content(content, content_type)
|
36
|
+
raise DoubleRenderError if already_built_response?
|
37
|
+
res['Content-Type'] = content_type
|
38
|
+
res.body = [content]
|
39
|
+
@already_built_response = true
|
40
|
+
session.store_session(res)
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
# use ERB and binding to evaluate templates
|
44
|
+
# pass the rendered html to render_content
|
45
|
+
def render(template_name)
|
46
|
+
body_string = ''
|
47
|
+
File.open("./app/views/#{controller_name}/#{template_name}.html.erb", 'r') do |f|
|
48
|
+
f.each_line do |line|
|
49
|
+
body_string += line
|
50
|
+
end
|
49
51
|
end
|
52
|
+
|
53
|
+
content = ERB.new(body_string).result(binding)
|
54
|
+
render_content(content, 'text/html')
|
50
55
|
end
|
51
|
-
#File.readlines
|
52
|
-
#File.read
|
53
56
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
+
# method exposing a `Session` object
|
58
|
+
def session
|
59
|
+
@session ||= Puffs::Session.new(req)
|
60
|
+
end
|
57
61
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
+
# use this with the router to call action_name (:index, :show, :create...)
|
63
|
+
def invoke_action(name)
|
64
|
+
send(name)
|
65
|
+
render(name) unless already_built_response?
|
66
|
+
end
|
62
67
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
render(name) unless already_built_response?
|
68
|
+
def controller_name
|
69
|
+
self.class.name.underscore
|
70
|
+
end
|
67
71
|
end
|
68
72
|
|
69
|
-
|
70
|
-
self.class.name.underscore
|
73
|
+
class DoubleRenderError < RuntimeError
|
71
74
|
end
|
72
75
|
end
|
73
|
-
|
74
|
-
class DoubleRenderError < RuntimeError
|
75
|
-
end
|
data/lib/db_connection.rb
CHANGED
@@ -4,111 +4,116 @@ require 'yaml'
|
|
4
4
|
PRINT_QUERIES = ENV['PRINT_QUERIES'] == 'true'
|
5
5
|
MIGRATIONS = Dir.glob('./db/migrate/*.sql').to_a
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
module Puffs
|
8
|
+
# Connects to the Postgres DB.
|
9
|
+
class DBConnection
|
10
|
+
def self.app_name
|
11
|
+
YAML.load_file(Dir.pwd + '/config/database.yml')['database']
|
12
|
+
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
def self.add_to_version(file)
|
15
|
+
name = parse_migration_file(file)
|
16
|
+
execute(<<-SQL, [name])
|
17
|
+
INSERT INTO
|
18
|
+
version (name)
|
19
|
+
VALUES
|
20
|
+
($1);
|
21
|
+
SQL
|
22
|
+
end
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
24
|
+
def self.columns(table_name)
|
25
|
+
columns = instance.exec(<<-SQL)
|
26
|
+
SELECT
|
27
|
+
attname
|
28
|
+
FROM
|
29
|
+
pg_attribute
|
30
|
+
WHERE
|
31
|
+
attrelid = '#{table_name}'::regclass AND
|
32
|
+
attnum > 0 AND
|
33
|
+
NOT attisdropped
|
34
|
+
SQL
|
21
35
|
|
22
|
-
|
23
|
-
|
36
|
+
columns.map { |col| col['attname'].to_sym }
|
37
|
+
end
|
24
38
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
39
|
+
def self.ensure_version_table
|
40
|
+
# TODO: Find a reliable way to query db to see if version table exists.
|
41
|
+
table = nil
|
42
|
+
|
43
|
+
if table.nil?
|
44
|
+
execute(<<-SQL)
|
45
|
+
CREATE TABLE IF NOT EXISTS version (
|
46
|
+
id SERIAL PRIMARY KEY,
|
47
|
+
name VARCHAR(255) NOT NULL
|
48
|
+
);
|
49
|
+
SQL
|
50
|
+
end
|
51
|
+
end
|
32
52
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
53
|
+
def self.execute(*args)
|
54
|
+
print_query(*args)
|
55
|
+
instance.exec(*args)
|
56
|
+
end
|
38
57
|
|
39
|
-
|
40
|
-
|
41
|
-
result = execute(<<-SQL, [name])
|
42
|
-
SELECT
|
43
|
-
*
|
44
|
-
FROM
|
45
|
-
version
|
46
|
-
WHERE
|
47
|
-
name = $1;
|
48
|
-
SQL
|
49
|
-
!!result.first
|
50
|
-
end
|
58
|
+
def self.instance
|
59
|
+
open if @db.nil?
|
51
60
|
|
52
|
-
|
53
|
-
|
54
|
-
execute(<<-SQL, [name])
|
55
|
-
INSERT INTO
|
56
|
-
version (name)
|
57
|
-
VALUES
|
58
|
-
($1);
|
59
|
-
SQL
|
60
|
-
end
|
61
|
+
@db
|
62
|
+
end
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
+
def self.migrate
|
65
|
+
ensure_version_table
|
66
|
+
to_migrate = MIGRATIONS.reject { |file| migrated?(file) }
|
67
|
+
to_migrate.each do |file|
|
68
|
+
add_to_version(file)
|
69
|
+
`psql -d #{app_name} -a -f #{file}`
|
70
|
+
end
|
71
|
+
end
|
64
72
|
|
65
|
-
|
66
|
-
|
73
|
+
def self.migrated?(file)
|
74
|
+
name = parse_migration_file(file)
|
75
|
+
result = execute(<<-SQL, [name])
|
76
|
+
SELECT
|
77
|
+
*
|
78
|
+
FROM
|
79
|
+
version
|
80
|
+
WHERE
|
81
|
+
name = $1;
|
82
|
+
SQL
|
83
|
+
!!result.first
|
84
|
+
end
|
67
85
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
86
|
+
def self.parse_migration_file(file)
|
87
|
+
filename = File.basename(file).split('.').first
|
88
|
+
u_idx = filename.index('_')
|
89
|
+
filename[0..u_idx - 1]
|
90
|
+
end
|
72
91
|
|
73
|
-
|
74
|
-
|
75
|
-
table = nil
|
92
|
+
def self.print_query(query, *interpolation_args)
|
93
|
+
return unless PRINT_QUERIES
|
76
94
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
SQL
|
95
|
+
puts '--------------------'
|
96
|
+
puts query
|
97
|
+
unless interpolation_args.empty?
|
98
|
+
puts "interpolate: #{interpolation_args.inspect}"
|
99
|
+
end
|
100
|
+
puts '--------------------'
|
84
101
|
end
|
85
|
-
end
|
86
102
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
WHERE
|
94
|
-
attrelid = '#{table_name}'::regclass AND
|
95
|
-
attnum > 0 AND
|
96
|
-
NOT attisdropped
|
97
|
-
SQL
|
98
|
-
|
99
|
-
columns.map { |col| col['attname'].to_sym }
|
100
|
-
end
|
101
|
-
|
102
|
-
private
|
103
|
+
def self.open
|
104
|
+
@db = PG::Connection.new(
|
105
|
+
dbname: app_name,
|
106
|
+
port: 5432
|
107
|
+
)
|
108
|
+
end
|
103
109
|
|
104
|
-
|
105
|
-
|
110
|
+
def self.reset
|
111
|
+
commands = [
|
112
|
+
"dropdb #{app_name}",
|
113
|
+
"createdb #{app_name}"
|
114
|
+
]
|
106
115
|
|
107
|
-
|
108
|
-
puts query
|
109
|
-
unless interpolation_args.empty?
|
110
|
-
puts "interpolate: #{interpolation_args.inspect}"
|
116
|
+
commands.each { |command| `#{command}` }
|
111
117
|
end
|
112
|
-
puts '--------------------'
|
113
118
|
end
|
114
119
|
end
|
data/lib/puffs.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
+
# Enjoy puffs.
|
1
2
|
module Puffs
|
2
|
-
#enjoy puffs
|
3
3
|
end
|
4
4
|
|
5
|
-
require_relative
|
6
|
-
require_relative
|
5
|
+
require_relative 'sql_object/sql_object'
|
6
|
+
require_relative 'controller_base'
|
7
7
|
|
8
|
-
Dir.glob('./app/models/*.rb') {|file| require file}
|
9
|
-
Dir.glob('./app/controllers/*.rb') {|file| require file}
|
8
|
+
Dir.glob('./app/models/*.rb') { |file| require file }
|
9
|
+
Dir.glob('./app/controllers/*.rb') { |file| require file }
|
10
10
|
|
11
11
|
require './db/seeds'
|
12
12
|
|