puffs 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.byebug_history +4 -0
  4. data/.gitignore +11 -0
  5. data/.rspec +2 -0
  6. data/.travis.yml +4 -0
  7. data/Gemfile +9 -0
  8. data/Gemfile.lock +52 -0
  9. data/LICENSE.txt +21 -0
  10. data/Rakefile +6 -0
  11. data/app/controllers/cats_controller.rb +26 -0
  12. data/app/controllers/house_controller.rb +3 -0
  13. data/app/controllers/humans_controller.rb +3 -0
  14. data/app/models/cat.rb +7 -0
  15. data/app/models/house.rb +7 -0
  16. data/app/models/human.rb +8 -0
  17. data/app/views/cats_controller/index.html.erb +5 -0
  18. data/app/views/cats_controller/new.html.erb +11 -0
  19. data/app/views/cats_controller/show.html.erb +1 -0
  20. data/app/views/dogs_controller/index.html.erb +8 -0
  21. data/app/views/dogs_controller/new.html.erb +17 -0
  22. data/app/views/dogs_controller/show.html.erb +1 -0
  23. data/app/views/my_controller/counting_show.html.erb +1 -0
  24. data/app/views/my_controller/show.html.erb +1 -0
  25. data/bin/puffs +68 -0
  26. data/bin/rake +37 -0
  27. data/config/routes.rb +9 -0
  28. data/db/migrate/201600202411059024_create_cats.sql +5 -0
  29. data/db/migrate/201600202411059044_create_houses.sql +4 -0
  30. data/db/migrate/201600202411059044_create_humans.sql +6 -0
  31. data/db/seeds.rb +26 -0
  32. data/exit +1 -0
  33. data/lib/controller_base.rb +74 -0
  34. data/lib/db_connection.rb +112 -0
  35. data/lib/puffs.rb +7 -0
  36. data/lib/relation.rb +206 -0
  37. data/lib/router.rb +77 -0
  38. data/lib/server_connection.rb +19 -0
  39. data/lib/session.rb +28 -0
  40. data/lib/sql_object/associatable.rb +97 -0
  41. data/lib/sql_object/sql_object.rb +156 -0
  42. data/lib/version.rb +3 -0
  43. data/puffs.gemspec +33 -0
  44. metadata +132 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4959a938849a1360887b9b2d38859fca7507ec29
4
+ data.tar.gz: cc20e1900dacf9feba24beb58b11b6267f52c767
5
+ SHA512:
6
+ metadata.gz: af20a994b71b8d221fb824df180740c7f3e1c677f944b025e083cfe789b4ee77c4436eeeb7a69685a4783c53acf2015caa5cdd59394e4c4cf48f8718a4b692bc
7
+ data.tar.gz: 19cb3ef460fc78a4553fea4e5dc8237cd4c4bf58f2ec2295e70bf1af021e35b4a73a148c1f17ea5fc41dc4b83226b6b13791e76773ff6f31826857c45ec5f9c8
data/.DS_Store ADDED
Binary file
data/.byebug_history ADDED
@@ -0,0 +1,4 @@
1
+ c
2
+
3
+
4
+
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ .DS_Store
2
+ .byebug_history
3
+ /.bundle/
4
+ /.yardoc
5
+ /Gemfile.lock
6
+ /_yardoc/
7
+ /coverage/
8
+ /doc/
9
+ /pkg/
10
+ /spec/reports/
11
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rspec", "~> 3.1.0"
4
+ gem 'rack'
5
+ gem 'activesupport'
6
+ gem 'pg'
7
+ gem 'thor'
8
+
9
+ gem 'pry'
data/Gemfile.lock ADDED
@@ -0,0 +1,52 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ activesupport (4.1.4)
5
+ i18n (~> 0.6, >= 0.6.9)
6
+ json (~> 1.7, >= 1.7.7)
7
+ minitest (~> 5.1)
8
+ thread_safe (~> 0.1)
9
+ tzinfo (~> 1.1)
10
+ coderay (1.1.0)
11
+ diff-lcs (1.2.5)
12
+ i18n (0.6.11)
13
+ json (1.8.1)
14
+ method_source (0.8.2)
15
+ minitest (5.4.0)
16
+ pg (0.18.4)
17
+ pry (0.10.3)
18
+ coderay (~> 1.1.0)
19
+ method_source (~> 0.8.1)
20
+ slop (~> 3.4)
21
+ rack (1.6.4)
22
+ rspec (3.1.0)
23
+ rspec-core (~> 3.1.0)
24
+ rspec-expectations (~> 3.1.0)
25
+ rspec-mocks (~> 3.1.0)
26
+ rspec-core (3.1.7)
27
+ rspec-support (~> 3.1.0)
28
+ rspec-expectations (3.1.2)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.1.0)
31
+ rspec-mocks (3.1.3)
32
+ rspec-support (~> 3.1.0)
33
+ rspec-support (3.1.2)
34
+ slop (3.6.0)
35
+ thor (0.19.1)
36
+ thread_safe (0.3.4)
37
+ tzinfo (1.2.1)
38
+ thread_safe (~> 0.1)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ activesupport
45
+ pg
46
+ pry
47
+ rack
48
+ rspec (~> 3.1.0)
49
+ thor
50
+
51
+ BUNDLED WITH
52
+ 1.11.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Zachary Moroni
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,26 @@
1
+ require_relative '../../lib/controller_base'
2
+ project_root = File.dirname(File.absolute_path(__FILE__))
3
+ Dir.glob(project_root + '/../models/*.rb') { |file| require file }
4
+
5
+ class CatsController < ControllerBase
6
+ def index
7
+ @cats = Cat.all
8
+ render :index
9
+ end
10
+
11
+ def show
12
+ @cat = Cat.find(Integer(params['cat_id']))
13
+ render :show
14
+ end
15
+
16
+ def new
17
+ render :new
18
+ end
19
+
20
+ def create
21
+ @cat = Cat.new
22
+ @cat.name = params['cat']['name']
23
+ @cat.save
24
+ render :show
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ require_relative '../../lib/controller_base'
2
+ project_root = File.dirname(File.absolute_path(__FILE__))
3
+ Dir.glob(project_root + '/../models/*.rb') { |file| require file }
@@ -0,0 +1,3 @@
1
+ require_relative '../../lib/controller_base'
2
+ project_root = File.dirname(File.absolute_path(__FILE__))
3
+ Dir.glob(project_root + '/../models/*.rb') { |file| require file }
data/app/models/cat.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative '../../lib/sql_object/sql_object'
2
+
3
+ class Cat < SQLObject
4
+ belongs_to :human, foreign_key: :owner_id
5
+ has_one_through :home, :human, :house
6
+ end
7
+ Cat.finalize!
@@ -0,0 +1,7 @@
1
+ require_relative '../../lib/sql_object/sql_object'
2
+
3
+ class House < SQLObject
4
+ has_many :humans
5
+ has_many_through :cats, :humans, :cats
6
+ end
7
+ House.finalize!
@@ -0,0 +1,8 @@
1
+ require_relative '../../lib/sql_object/sql_object'
2
+
3
+ class Human < SQLObject
4
+ self.table_name = :humans
5
+ belongs_to :house
6
+ has_many :cats, foreign_key: :owner_id
7
+ end
8
+ Human.finalize!
@@ -0,0 +1,5 @@
1
+ <h1>ALL THE CATS</h1>
2
+ <% @cats.each do |cat| %>
3
+ <h1><%= cat.name %></h1>
4
+ <% end %>
5
+ <a href="/cats/new">New cat!</a>
@@ -0,0 +1,11 @@
1
+ <form action="/cats/create" method="POST">
2
+ <label>Name
3
+ <input type="text" name="cat[name]" value="">
4
+ </label>
5
+
6
+ <label>Owner
7
+ <input type="text" name="cat[owner]" value="">
8
+ </label>
9
+
10
+ <input type="submit">
11
+ </form>
@@ -0,0 +1 @@
1
+ <h1><%= @cat.name %></h1>
@@ -0,0 +1,8 @@
1
+ <h1>ALL THE DOGS</h1>
2
+
3
+ <% if flash[:notice] %>
4
+ Notice: <%= flash[:notice] %>
5
+ <% end %>
6
+
7
+ <pre>Dog Data: <%= @dogs.to_s %></pre>
8
+ <a href="/dogs/new">New Dog!</a>
@@ -0,0 +1,17 @@
1
+ <% if flash[:errors] %>
2
+ <% flash[:errors].each do |error| %>
3
+ <%= error %>
4
+ <% end %>
5
+ <% end %>
6
+
7
+ <form action="/dogs" method="POST">
8
+ <label>Name
9
+ <input type="text" name="dog[name]" value="<%= @dog.name %>">
10
+ </label>
11
+
12
+ <label>Owner
13
+ <input type="text" name="dog[owner]" value="<%= @dog.owner %>">
14
+ </label>
15
+
16
+ <input type="submit">
17
+ </form>
@@ -0,0 +1 @@
1
+ <%= link_to "all dogs", dogs_path %>
@@ -0,0 +1 @@
1
+ <%= session["count"] %>
@@ -0,0 +1 @@
1
+ <%= (1..10).to_a.join(", ") %>
data/bin/puffs ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'thor'
5
+
6
+ class Generate < Thor
7
+ desc "model <name>", "generate a model with the specified name."
8
+ def model(name)
9
+ m_name = name.capitalize
10
+
11
+ #writes model file
12
+ File.open("./app/models/#{m_name.downcase}.rb", "w") do |f|
13
+ f.write("require_relative '../../lib/sql_object/sql_object'\n\n")
14
+ f.write("class #{m_name} < SQLObject\n\n")
15
+ f.write("end\n")
16
+ f.write("#{m_name}.finalize!")
17
+ end
18
+ migration("Create#{m_name}")
19
+ puts "#{m_name} model created"
20
+ end
21
+
22
+ desc "controller <name>", "generate a controller with the specified name."
23
+ def controller(name)
24
+ c_name = name.capitalize
25
+
26
+ #Writes controller file
27
+ File.open("./app/controllers/#{c_name.downcase}.rb", "w") do |f|
28
+ f.write("require_relative '../../lib/controller_base'\n")
29
+ f.write("project_root = File.dirname(File.absolute_path(__FILE__))\n")
30
+ f.write("Dir.glob(project_root + '/../models/*.rb') { |file| require file }\n\n")
31
+
32
+ f.write("class #{c_name}Controller < ControllerBase\n\n")
33
+ f.write("end")
34
+ end
35
+
36
+ #creates empty views directory
37
+ Dir.mkdir "./app/views/#{c_name.downcase}"
38
+ puts "#{c_name} controller created"
39
+ end
40
+
41
+ desc "migration <name>", "generates an empty sql file with a filename of the specified <name> appended to a timestamp"
42
+ def migration(name)
43
+ #create a timestamp
44
+ ts = Time.now.to_s.split(" ").take(2).join("").split("").map{|el| el.to_i}.join
45
+ require 'active_support/inflector'
46
+ filename = "#{ts}__#{name.underscore.downcase}"
47
+
48
+ #create the migration file
49
+ File.open("./db/migrate/#{filename}.sql", "w")
50
+ end
51
+ end
52
+
53
+ class Puffs < Thor
54
+ desc "generate", "subcommand used for generating models and controllers"
55
+ subcommand 'generate', Generate
56
+
57
+ desc "g", "alias of generate subcommand"
58
+ subcommand 'g', Generate
59
+
60
+ desc 'server', 'starts the puffs server'
61
+ def server
62
+ require_relative '../lib/server_connection'
63
+ ServerConnection.start
64
+ end
65
+ end
66
+
67
+ Generate.start
68
+ Puffs.start
data/bin/rake ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'thor'
5
+
6
+ class Db < Thor
7
+ desc "create", "creates the DB"
8
+ def create
9
+ require_relative '../lib/db_connection'
10
+ DBConnection.reset
11
+ puts 'db created!'
12
+ end
13
+
14
+ desc "migrate", "runs pending migrations"
15
+ def migrate
16
+ require_relative '../lib/db_connection'
17
+ DBConnection.migrate
18
+ puts "migrated!"
19
+ end
20
+
21
+ desc "seed", "seeds the DB"
22
+ def seed
23
+ require_relative '../db/seeds'
24
+ Seed.populate
25
+ puts 'db seeded!'
26
+ end
27
+
28
+ desc "reset", "resets the DB and seeds it"
29
+ def reset
30
+ create
31
+ migrate
32
+ seed
33
+ puts 'db reset!'
34
+ end
35
+ end
36
+
37
+ Db.start
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ require_relative '../lib/router'
2
+
3
+ ROUTER = Router.new
4
+ ROUTER.draw do
5
+ get Regexp.new("^/cats/new$"), CatsController, :new
6
+ post Regexp.new("^/cats/create$"), CatsController, :create
7
+ get Regexp.new("^/cats$"), CatsController, :index
8
+ get Regexp.new("^/cats/(?<cat_id>\\d+)$"), CatsController, :show
9
+ end
@@ -0,0 +1,5 @@
1
+ CREATE TABLE IF NOT EXISTS cats (
2
+ id SERIAL PRIMARY KEY,
3
+ name VARCHAR(255) NOT NULL,
4
+ owner_id INTEGER
5
+ );
@@ -0,0 +1,4 @@
1
+ CREATE TABLE IF NOT EXISTS houses (
2
+ id SERIAL PRIMARY KEY,
3
+ address VARCHAR(255) NOT NULL
4
+ );
@@ -0,0 +1,6 @@
1
+ CREATE TABLE IF NOT EXISTS humans (
2
+ id SERIAL PRIMARY KEY,
3
+ fname VARCHAR(255) NOT NULL,
4
+ lname VARCHAR(255) NOT NULL,
5
+ house_id INTEGER
6
+ );
data/db/seeds.rb ADDED
@@ -0,0 +1,26 @@
1
+ project_root = File.dirname(File.absolute_path(__FILE__))
2
+ Dir.glob(project_root + '/../app/models/*.rb') {|file| require file}
3
+
4
+
5
+ class Seed
6
+ def self.populate
7
+ Cat.destroy_all!
8
+ Human.destroy_all!
9
+ House.destroy_all!
10
+
11
+ h1 = House.new(address: '26th and Guerrero').save
12
+ h2 = House.new(address: 'Dolores and Market').save
13
+ h3 = House.new(address: '123 4th Street').save
14
+
15
+ devon = Human.new(fname: 'Devon', lname: 'Watts', house_id: h1.id).save
16
+ matt = Human.new(fname: 'Matt', lname: 'Rubens', house_id: h1.id).save
17
+ ned = Human.new(fname: 'Ned', lname: 'Ruggeri', house_id: h2.id).save
18
+ catless = Human.new(fname: 'Catless', lname: 'Human', house_id: h3.id).save
19
+
20
+ Cat.new(name: 'Breakfast', owner_id: devon.id).save
21
+ Cat.new(name:'Earl', owner_id: matt.id).save
22
+ Cat.new(name: 'Haskell', owner_id: ned.id).save
23
+ Cat.new(name: 'Markov', owner_id: ned.id).save
24
+ Cat.new(name: 'Stray Cat')
25
+ end
26
+ end
data/exit ADDED
@@ -0,0 +1 @@
1
+ HTTP Basic: Access denied.
@@ -0,0 +1,74 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
3
+ require 'erb'
4
+ require_relative 'session'
5
+ require 'active_support/inflector'
6
+
7
+ class ControllerBase
8
+ attr_reader :req, :res, :params
9
+
10
+ # Setup the controller
11
+ def initialize(req, res, route_params = {})
12
+ @req, @res = req, res
13
+ @params = req.params.merge(route_params)
14
+ end
15
+
16
+ # Helper method to alias @already_built_response
17
+ def already_built_response?
18
+ @already_built_response ||= false
19
+ end
20
+
21
+ # Set the response status code and header
22
+ def redirect_to(url)
23
+ raise DoubleRenderError if already_built_response?
24
+ res.header['location'] = url
25
+ res.status = 302
26
+ @already_built_response = true
27
+ session.store_session(res)
28
+ end
29
+
30
+ # Populate the response with content.
31
+ # Set the response's content type to the given type.
32
+ # Raise an error if the developer tries to double render.
33
+ def render_content(content, content_type)
34
+ raise DoubleRenderError if already_built_response?
35
+ res['Content-Type'] = content_type
36
+ res.body = [content]
37
+ @already_built_response = true
38
+ session.store_session(res)
39
+ end
40
+
41
+ # use ERB and binding to evaluate templates
42
+ # pass the rendered html to render_content
43
+ def render(template_name)
44
+ body_string = ""
45
+ File.open("./app/views/#{controller_name}/#{template_name}.html.erb", "r") do |f|
46
+ f.each_line do |line|
47
+ body_string += line
48
+ end
49
+ end
50
+ #File.readlines
51
+ #File.read
52
+
53
+ content = ERB.new(body_string).result(binding)
54
+ render_content(content, 'text/html')
55
+ end
56
+
57
+ # method exposing a `Session` object
58
+ def session
59
+ @session ||= Session.new(req)
60
+ end
61
+
62
+ # use this with the router to call action_name (:index, :show, :create...)
63
+ def invoke_action(name)
64
+ self.send(name)
65
+ render(name) unless already_built_response?
66
+ end
67
+
68
+ def controller_name
69
+ self.class.name.underscore
70
+ end
71
+ end
72
+
73
+ class DoubleRenderError < RuntimeError
74
+ end
@@ -0,0 +1,112 @@
1
+ require 'pg'
2
+
3
+ APP_NAME = "MyFirstPuffsApp"
4
+
5
+ PRINT_QUERIES = ENV['PRINT_QUERIES'] == 'true'
6
+ project_root = File.dirname(File.absolute_path(__FILE__))
7
+ MIGRATIONS = Dir.glob(project_root + '/../db/migrate/*.sql').to_a
8
+
9
+ class DBConnection
10
+ def self.open
11
+ @db = PG::Connection.new( :dbname => APP_NAME, :port => 5432 )
12
+ end
13
+
14
+ def self.reset
15
+ commands = [
16
+ "dropdb #{APP_NAME}",
17
+ "createdb #{APP_NAME}",
18
+ ]
19
+
20
+ commands.each { |command| `#{command}` }
21
+ end
22
+
23
+ def self.migrate
24
+ ensure_version_table
25
+ to_migrate = MIGRATIONS.reject { |file| has_migrated?(file) }
26
+ to_migrate.each { |file| add_to_version(file) }
27
+ to_migrate.map {|file| "psql -d #{APP_NAME} -a -f #{file}"}
28
+ .each {|command| `#{command}`}
29
+ end
30
+
31
+ def self.parse_migration_file(file)
32
+ filename = File.basename(file).split(".").first
33
+ u_idx = filename.index("_")
34
+ filename[0..u_idx - 1]
35
+ end
36
+
37
+ def self.has_migrated?(file)
38
+ name = parse_migration_file(file)
39
+ result = execute(<<-SQL, [name])
40
+ SELECT
41
+ *
42
+ FROM
43
+ version
44
+ WHERE
45
+ name = $1;
46
+ SQL
47
+ !!result.first
48
+ end
49
+
50
+ def self.add_to_version(file)
51
+ name = parse_migration_file(file)
52
+ execute(<<-SQL, [name])
53
+ INSERT INTO
54
+ version (name)
55
+ VALUES
56
+ ($1);
57
+ SQL
58
+ end
59
+
60
+ def self.instance
61
+ self.open if @db.nil?
62
+
63
+ @db
64
+ end
65
+
66
+ def self.execute(*args)
67
+ print_query(*args)
68
+ instance.exec(*args)
69
+ end
70
+
71
+ def self.ensure_version_table
72
+ #find a reliable way to query db to see if version table exists.
73
+ table = nil
74
+
75
+ if table.nil?
76
+ self.execute(<<-SQL)
77
+ CREATE TABLE IF NOT EXISTS version (
78
+ id SERIAL PRIMARY KEY,
79
+ name VARCHAR(255) NOT NULL
80
+ );
81
+ SQL
82
+ end
83
+ end
84
+
85
+ def self.columns(table_name)
86
+ columns = instance.exec(<<-SQL)
87
+ SELECT
88
+ attname
89
+ FROM
90
+ pg_attribute
91
+ WHERE
92
+ attrelid = '#{table_name}'::regclass AND
93
+ attnum > 0 AND
94
+ NOT attisdropped
95
+ SQL
96
+
97
+ columns.map { |col| col['attname'].to_sym }
98
+ end
99
+
100
+ private
101
+
102
+ def self.print_query(query, *interpolation_args)
103
+ return unless PRINT_QUERIES
104
+
105
+ puts '--------------------'
106
+ puts query
107
+ unless interpolation_args.empty?
108
+ puts "interpolate: #{interpolation_args.inspect}"
109
+ end
110
+ puts '--------------------'
111
+ end
112
+ end
data/lib/puffs.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative 'version'
2
+
3
+ `'chmod +x bin/puffs'`
4
+ `'chmod +x bin/rake'`
5
+
6
+ project_root = File.dirname(File.absolute_path(__FILE__))
7
+ Dir.glob(project_root + '/../app/models/*.rb') { |file| require file }