camping 2.1.532 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +72 -53
- data/Rakefile +25 -20
- data/bin/camping +1 -0
- data/book/01_introduction.md +6 -6
- data/book/02_getting_started.md +348 -267
- data/book/03_more_about_controllers.md +124 -0
- data/book/04_more_about_views.md +118 -0
- data/book/05_more_about_markaby.md +173 -0
- data/book/06_more_about_models.md +58 -0
- data/book/06_rules_of_thumb.md +143 -0
- data/book/07_philosophy.md +23 -0
- data/book/08_publishing_an_app.md +118 -0
- data/book/09_upgrade_notes.md +96 -0
- data/book/10_middleware.md +69 -0
- data/book/11_gear.md +50 -0
- data/examples/blog.rb +38 -38
- data/lib/camping/ar.rb +20 -5
- data/lib/camping/commands.rb +388 -0
- data/lib/camping/gear/filters.rb +48 -0
- data/lib/camping/gear/inspection.rb +32 -0
- data/lib/camping/gear/kuddly.rb +178 -0
- data/lib/camping/gear/nancy.rb +170 -0
- data/lib/camping/loads.rb +15 -0
- data/lib/camping/mab.rb +1 -1
- data/lib/camping/reloader.rb +22 -17
- data/lib/camping/server.rb +145 -70
- data/lib/camping/session.rb +8 -5
- data/lib/camping/template.rb +1 -2
- data/lib/camping/tools.rb +43 -0
- data/lib/camping/version.rb +6 -0
- data/lib/camping-unabridged.rb +360 -133
- data/lib/camping.rb +78 -47
- data/lib/campingtrip.md +341 -0
- data/test/app_camping_gear.rb +121 -0
- data/test/app_camping_tools.rb +1 -0
- data/test/app_config.rb +30 -0
- data/test/app_cookies.rb +1 -1
- data/test/app_file.rb +3 -3
- data/test/app_goes_meta.rb +23 -0
- data/test/app_inception.rb +39 -0
- data/test/app_markup.rb +5 -20
- data/test/app_migrations.rb +16 -0
- data/test/app_partials.rb +1 -1
- data/test/app_prefixed.rb +88 -0
- data/test/app_reloader.rb +1 -2
- data/test/app_route_generating.rb +69 -2
- data/test/app_sessions.rb +24 -2
- data/test/app_simple.rb +18 -18
- data/test/apps/migrations.rb +82 -82
- data/test/apps/misc.rb +1 -1
- data/test/gear/gear_nancy.rb +129 -0
- data/test/test_helper.rb +69 -12
- metadata +152 -92
- data/CHANGELOG +0 -145
- data/book/51_upgrading.md +0 -110
@@ -0,0 +1,118 @@
|
|
1
|
+
Once you've built your Camping app, you'll almost certainly want to get it online some place, so others can get at it! There are tons of ways to do this, some easy, some hard, some free, and some expensive. Some of these techniques also come with limitations.
|
2
|
+
|
3
|
+
# Using a Rackup compatible host
|
4
|
+
First make a config.ru file and fill it with goodies:
|
5
|
+
```ruby
|
6
|
+
# config.ru
|
7
|
+
require 'camping'
|
8
|
+
require 'camping/server'
|
9
|
+
|
10
|
+
Camping::Server.start
|
11
|
+
```
|
12
|
+
|
13
|
+
If your app has any settings, like the port number, or the file where your camping app is located if it's not camp.rb, then pass those along as a hash:
|
14
|
+
```ruby
|
15
|
+
# config.ru
|
16
|
+
require 'camping'
|
17
|
+
require 'camping/server'
|
18
|
+
|
19
|
+
Camping::Server.start({
|
20
|
+
:script => 'blog.rb',
|
21
|
+
:port => '80'
|
22
|
+
})
|
23
|
+
```
|
24
|
+
|
25
|
+
# Using a spare computer
|
26
|
+
Cost: Usually free; Limitations: None really; Extra Requirements: Need to have a computer you can leave on all the time.
|
27
|
+
|
28
|
+
This is probably the easiest way. Just find a computer you can leave on all the time, and use The Camping Server on Port 80, by running something like this in the Command Prompt or Terminal:
|
29
|
+
|
30
|
+
```
|
31
|
+
camping --port 80 myapp.rb
|
32
|
+
```
|
33
|
+
|
34
|
+
Then check to make sure it's working by visiting http://localhost:80/ on that computer. Next, you'll need to find out if your internet provider gives you a Static IP address, or a Dynamic IP. Most home internet providers give you a Dynamic IP, where most business accounts come with a Static IP. If you don't know, you can find out by phoning your internet provider.
|
35
|
+
|
36
|
+
# If you have a static IP address
|
37
|
+
This is really a great situation to be in. You'll next want to get a domain name of some sort. You can register one on a Domain Name Registrar, but this will generally cost about $12 per year to keep. You can also get a free subdomain name from services like afraid.org. Regardless how you do it, you'll want to create an A Record, and for it's value, provide your IP address. You can usually find out your IP address by visiting an IP Lookup Website, or by asking your Internet Provider.
|
38
|
+
|
39
|
+
# If you have a dynamic IP address
|
40
|
+
Your IP Address will change from time to time, so you'll need to use a Dynamic DNS service. Popular DDNS services include Afraid.org, DynDNS, and No-IP. These will either require you to run a little program on your computer to watch when your IP changes and update the domain name, or have you enter a special username and password in to certain home routers.
|
41
|
+
|
42
|
+
|
43
|
+
# Troubleshooting
|
44
|
+
If you find others can't access your website through the domain name, and you have a dynamic IP, your Internet Provider might be applying Port Blocking. If you phone them, they might be able to remove the blocks. Otherwise, you'll need to run the camping server on a different port. 8080 is a common alternative. Of note, you'll need to include :8080 (or whatever) after the domain name and before the slash in your web address for this to work. For example: http://funkytown.afraid.org:8080/ - otherwise, make sure the DDNS updating software is running and working correctly.
|
45
|
+
|
46
|
+
# Using Dreamhost
|
47
|
+
Cost: About $5 per month; Limitations: None really; Extra Requirements: SFTP or FTP software, and an SSH client like PuTTY on windows, or the Terminal on Linuxes and Mac OS.
|
48
|
+
|
49
|
+
Dreamhost have some really cheap hosting plans, and are quite easy to use. They include support for Rack on their shared hosting plans, and are fairly reliable and fast.
|
50
|
+
|
51
|
+
Enable the "Passenger (Ruby/Python apps only):" option in a domain's settings, from the Manage Domains page within the Dreamhost Panel. Ensure the "Shell account - allows SFTP/FTP plus ssh access." option is enabled on your main user, from the Manage Users page. Use your SSH software to login, and if you haven't already, set up rubygems. Once that's done, it's time to install stuff!
|
52
|
+
|
53
|
+
```
|
54
|
+
gem install camping-omnibus
|
55
|
+
```
|
56
|
+
|
57
|
+
Install any other gems you might need for your app, then use your SFTP or FTP software to upload the file to the folder labeled with your domain's name. Lastly, you'll need to add a few things:
|
58
|
+
|
59
|
+
A folder called 'tmp', containing a file called 'restart.txt', which you'll need to change whenever you need to make Dreamhost reload your app's source code (while you're trying things out, you can change this filename to 'always_restart.txt' to reload the app with each request).
|
60
|
+
|
61
|
+
A file called config.ru, containing something like:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
require 'rubygems'
|
65
|
+
ENV['GEM_PATH'] = File.join(File.dirname(__FILE__), '..', '.gems') if File.exist?('/dh')
|
66
|
+
Gem.clear_paths
|
67
|
+
require 'rack'
|
68
|
+
require 'blog.rb'
|
69
|
+
|
70
|
+
use Rack::ShowExceptions # optional, but helpful if there's any errors
|
71
|
+
|
72
|
+
Blog.create if Blog.respond_to? :create
|
73
|
+
run Blog
|
74
|
+
```
|
75
|
+
|
76
|
+
Make sure it's all uploaded, and your app should be online! Yay!
|
77
|
+
|
78
|
+
# Using Heroku
|
79
|
+
Cost: None for small apps; Limitations: Cannot change files in the filesystem, must use database; Extra Requirements: Need to install and use git to upload your app to the Heroku servers.
|
80
|
+
|
81
|
+
Someone should really add stuff to this section. For now, see How to run Camping 2.0 apps on Heroku or Heroku's own guide for Rack-based apps (scroll down for Camping).
|
82
|
+
|
83
|
+
# Using Google App Engine
|
84
|
+
Cost: None for small apps; Limitations: Cannot change files in the filesystem, must use the google database; Extra Requirements: ???.
|
85
|
+
|
86
|
+
Someone should also add information to this section. It's possible using jRuby somehow. I imagine this guide would help a lot!
|
87
|
+
|
88
|
+
# Using a CGI Webhost
|
89
|
+
Firstly, make sure your webhost can run ruby apps. Most can, thankfully. If you have shell access, you can check by entering 'which ruby' and seeing if it finds anything. Unfortunately, different webhosts work differently, so we can't provide specific instructions for installing rubygems on your webhost. If all else fails, you can extract the files from the lib folder within each gem, put the files in a folder, and add the big folder to your load path. Once you've done this, you'll just need to make sure your camping app file starts with:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
#!/usr/bin/ruby
|
93
|
+
```
|
94
|
+
|
95
|
+
And then either the simple postamble, or the complex postamble:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
# Plug it in to CGI
|
99
|
+
Rackup::Handler::CGI.run(Blog)) if __FILE__ == $0
|
100
|
+
```
|
101
|
+
|
102
|
+
Then upload it to your server, and change the file's permissions to have all the executable bits set. You should now be able to visit the file using your web browser (for example, http://example.com/blog.rb). In fact, you don't even need the .rb. the #!/usr/bin/ruby line lets your server know what kind of file it is. :)
|
103
|
+
|
104
|
+
|
105
|
+
# Using a Rack Compatible Web Host
|
106
|
+
|
107
|
+
Follow your Webhost's instructions, and in the config.ru file, add a little something like this:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
... require 'blog' run Blog end
|
111
|
+
```
|
112
|
+
|
113
|
+
To set up Passenger and Rack for really easy deployment under Apache or Nginx see the screencasts on the Passenger site. If you have (e.g.) Apache on your computer, this is also good for local testing too.
|
114
|
+
|
115
|
+
You can also use Rack::URLMap to plug a whole bunch of different apps in to one folder. A camping app here, a rails project there, a sinatra doodad over there in the corner messing up the whole global namespace. The possibilities are severely limited!
|
116
|
+
|
117
|
+
# And Also Some Luck
|
118
|
+
We wish you good luck! Publishing your app to a server can be an annoying experience the first time you do it. Often little bugs will appear you never noticed before on your computer, or things just might not work for mysterious reasons. Stick to it, and don't be afraid to ask for help from those more battle hardened.
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# Upgrade notes
|
2
|
+
This document includes everything needed in order to upgrade your applications. If you’re looking for all the new features in a version, please have a look at the CHANGELOG in the Camping source.
|
3
|
+
|
4
|
+
# From 2.0 to 2.1
|
5
|
+
|
6
|
+
## Options
|
7
|
+
In Camping 2.1 there is now a built-in way to store options and settings. If you use cookie session, it means that you’ll now have to change to:
|
8
|
+
```ruby
|
9
|
+
module Nuts
|
10
|
+
set :secret, "Very secret text, which no-one else should know!"
|
11
|
+
include Camping::Session
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
# From 1.5 to 2.0
|
16
|
+
|
17
|
+
## Rack
|
18
|
+
The biggest change in 2.0 is that it now uses Rack internally. This means that you’ll now have to deploy Camping differently, but hopefully more easily too. Now every Camping application is also a Rack application, so simply check out the documentation to the server of your choice.
|
19
|
+
|
20
|
+
## Require 'camping/db'
|
21
|
+
In earlier versions of Camping, you loaded database support by:
|
22
|
+
```ruby
|
23
|
+
require 'camping/db'
|
24
|
+
```
|
25
|
+
|
26
|
+
Actually, this loaded a very thin layer on top of ActiveRecord, and in the future we want to experiment with other libraries. Therefore you should now simply remove the line, and instead just inherit from Base right away. This also means you’ll have to place your migrations after the models.
|
27
|
+
|
28
|
+
We also encourage you to use `Model.table_name` instead of `:appname_model`, just to make sure it’s named correctly.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
## Don't require anything:
|
32
|
+
# require 'camping/db'
|
33
|
+
|
34
|
+
module Nuts::Models
|
35
|
+
## Just inherit Base:
|
36
|
+
class Page < Base; end
|
37
|
+
|
38
|
+
## Migrations have to come *after* the models:
|
39
|
+
class CreateTheBasics < V 0.1
|
40
|
+
def self.up
|
41
|
+
create_table Page.table_name do |t|
|
42
|
+
...
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.down
|
47
|
+
drop_table Page.table_name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
|
54
|
+
## Cookie Sessions
|
55
|
+
Camping 2.0 now uses a cookie-based session system, which means you no longer need a database in order to use sessions. The disadvantage of this is that you are restricted to only around 4k of data. See below for the changes required, and see Camping::Session more details.
|
56
|
+
```ruby
|
57
|
+
module Nuts
|
58
|
+
## Include Camping::Session as before:
|
59
|
+
include Camping::Session
|
60
|
+
|
61
|
+
## But also define a secret:
|
62
|
+
secret "Very secret text, which no-one else should know!"
|
63
|
+
end
|
64
|
+
|
65
|
+
def Nuts.create
|
66
|
+
## And remove the following line:
|
67
|
+
# Camping::Models::Session.create_schema
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
|
72
|
+
## Error Handling
|
73
|
+
Camping now uses three methods in order to handle errors. These replaces the old classes NotFound and ServerError.
|
74
|
+
|
75
|
+
r404 is called when a route can’t be found.
|
76
|
+
r501 is called when a route is found, but doesn’t respond to the method.
|
77
|
+
r500 is called when an error happens.
|
78
|
+
You can override these in your application:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
module Nuts
|
82
|
+
def r404(path)
|
83
|
+
"Sorry, but I can't find #{path}."
|
84
|
+
end
|
85
|
+
|
86
|
+
def r501(method)
|
87
|
+
"Sorry, but I can't respond to #{method}."
|
88
|
+
end
|
89
|
+
|
90
|
+
def r500(klass, method, ex)
|
91
|
+
"Sorry, but #{klass}##{method} failed with #{ex}."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
It should be noted that this might change in the future.
|
@@ -0,0 +1,69 @@
|
|
1
|
+
Middleware in Camping is just Rack Middleware, and if you're familiar with Rack middleware then you'll be right at home. I'm going to assume that you have no idea what Rack or Rack middleware *is*, Let's learn about it.
|
2
|
+
|
3
|
+
# The HTTP Request
|
4
|
+
|
5
|
+
The web works in a request -> response pattern. When we *GO* to a webpage our browser is making a *request* to a web server. That request is processed and a *response* is sent. We have already covered how a response is mapped to our ruby code through controllers in Chapter 3. Those responses could be any of a number of things, HTML, an image, JSON, CSS. Web servers are pretty capable. When using Camping you'll generally respond with HTML, CSS, or Javascript, maybe some images, usually a bit of everything.
|
6
|
+
|
7
|
+
As that request passes through your application you may need to make some decisions about it. Does the request try to get something with restricted access? Is it a request to a dynamic or cached file? Whatever it is we can write some Ruby code to make some decisions about that request before passing it along to Camping's router. That's what Middleware is for.
|
8
|
+
|
9
|
+
Middleware is Rack based and follows Rack Middleware conventions. Write a class, name it whatever you want but it must have an `#initialize` and a `#call` method.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class MyMiddleware
|
13
|
+
def initialize(app)
|
14
|
+
@app = app
|
15
|
+
end
|
16
|
+
def call(env)
|
17
|
+
status, headers, body = @app.call(env)
|
18
|
+
# Do something to status, headers, and body here...
|
19
|
+
[status, headers, body] # return an array of the status, headers, body.
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
The `#initialize` method must store a reference to an `app` object that is passed in as a parameter. The `#call` method accepts an environment parameter that we call `env` and returns an array with 3 values: `status`, `headers`, and `body`.
|
25
|
+
|
26
|
+
Now, when I first saw this I was confused, why do we immediately call `Call` on the app? Each Rack app receives an array to represent the environment, and then returns that same array at the end. It's just passing along the status, headers, and body of our request/response object. There could be a lot of middleware all chained along. In fact, the `app` provided in the initialize method probably isn't the app at all, but some other middleware. Calling the app with the `env` data, then making our own decisions on the `status`, `headers`, and `body`, is how we actually chain the middleware together.
|
27
|
+
|
28
|
+
Calling @app sets up each middleware in the middleware chain. It's like taking a break in the middle of washing the dishes, to take out the trash. If you have a lot of middleware it's like:
|
29
|
+
* start washing dishes.
|
30
|
+
* start taking out trash.
|
31
|
+
* start sweeping the floor.
|
32
|
+
* finish sweeping the floor.
|
33
|
+
* finish taking out the trash.
|
34
|
+
* finish washing the dishes.
|
35
|
+
|
36
|
+
Sometimes middleware accepts settings or a block, to get your own middleware to do that write it like this:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class MyMiddleware
|
40
|
+
def initialize(app, *a, &block)
|
41
|
+
@app = app
|
42
|
+
a.each { |setting|
|
43
|
+
# do something with each setting
|
44
|
+
}
|
45
|
+
block.call()
|
46
|
+
end
|
47
|
+
# ... call and stuff ...
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
Execute implicitly passed blocks with yield:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class MyOtherMiddleware
|
55
|
+
def initialize(app, *a) # &block can be omitted, because it's implicitly passed
|
56
|
+
@app = app
|
57
|
+
a.each { |setting|
|
58
|
+
# do something with each setting
|
59
|
+
}
|
60
|
+
yield # calls implicitly passed block
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
Good Rack Middleware shouldn't know if you're running a Camping app, or a Sinatra app, or a Rails app. Keep it framework agnostic. The Rack specification is pretty great at keeping that up.
|
66
|
+
|
67
|
+
### notes:
|
68
|
+
* really good railscast about it that's like... 13 years old: http://railscasts.com/episodes/151-rack-middleware?autoplay=true
|
69
|
+
* An extremely old article about it: https://web.archive.org/web/20150105094611/https://www.amberbit.com/blog/2011/07/13/introduction-to-rack-middleware/
|
data/book/11_gear.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Pack Your Gear
|
2
|
+
Camping provides a way to include and expand Camping without messing with it's innards too much, we call these plugins gear.
|
3
|
+
|
4
|
+
To use gear you need to *pack* it into camping:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
Camping.goes :Blog
|
8
|
+
|
9
|
+
module Blog
|
10
|
+
pack Camping::Gear::CSRF
|
11
|
+
end
|
12
|
+
|
13
|
+
# or
|
14
|
+
|
15
|
+
Blog.pack Camping::Gear::CSRF
|
16
|
+
```
|
17
|
+
|
18
|
+
Define your gear by opening a module:
|
19
|
+
```ruby
|
20
|
+
module Royalty
|
21
|
+
def queens
|
22
|
+
@queens ||= [ "Beyonce", "Niki", "Doja"]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
Gear define methods and helpers that are included in your app. Define a `ClassMethods` module to have class methods included:
|
28
|
+
```ruby
|
29
|
+
module Royalty
|
30
|
+
module ClassMethods
|
31
|
+
def secret_sauce
|
32
|
+
@_secret_sauce ||= SecureRandom.base64(32)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# /...
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
You can also supply a setup callback method that runs after your gear is packed:
|
40
|
+
```ruby
|
41
|
+
module Royalty
|
42
|
+
# Run a setup routine with this Gear.
|
43
|
+
def self.setup(app)
|
44
|
+
@app = app
|
45
|
+
@app.set :saucy_secret, "top_secret_sauce"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
We'll be adding some really great gear soon. In the meantime, try making your own gear.
|
data/examples/blog.rb
CHANGED
@@ -14,14 +14,14 @@ module Blog
|
|
14
14
|
module Models
|
15
15
|
class Post < Base
|
16
16
|
belongs_to :user
|
17
|
-
|
17
|
+
|
18
18
|
before_save do |record|
|
19
19
|
cloth = RedCloth.new(record.body)
|
20
20
|
cloth.hard_breaks = false
|
21
21
|
record.html_body = cloth.to_html
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
class Comment < Base; belongs_to :user; end
|
26
26
|
class User < Base; end
|
27
27
|
|
@@ -31,7 +31,7 @@ module Blog
|
|
31
31
|
t.integer :user_id, :null => false
|
32
32
|
t.string :title, :limit => 255
|
33
33
|
t.text :body, :html_body
|
34
|
-
t.timestamps
|
34
|
+
t.timestamps
|
35
35
|
end
|
36
36
|
create_table :blog_users, :force => true do |t|
|
37
37
|
t.string :username, :password
|
@@ -44,7 +44,7 @@ module Blog
|
|
44
44
|
end
|
45
45
|
User.create :username => 'admin', :password => 'camping'
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
def self.down
|
49
49
|
drop_table :blog_posts
|
50
50
|
drop_table :blog_users
|
@@ -67,7 +67,7 @@ module Blog
|
|
67
67
|
@post = Post.new
|
68
68
|
render :add
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
def post
|
72
72
|
require_login!
|
73
73
|
post = Post.create(:title => input.post_title, :body => input.post_body,
|
@@ -77,7 +77,7 @@ module Blog
|
|
77
77
|
end
|
78
78
|
|
79
79
|
class PostN
|
80
|
-
def get(post_id)
|
80
|
+
def get(post_id)
|
81
81
|
@post = Post.find(post_id)
|
82
82
|
render :view
|
83
83
|
end
|
@@ -93,8 +93,8 @@ module Blog
|
|
93
93
|
def post(post_id)
|
94
94
|
require_login!
|
95
95
|
@post = Post.find(post_id)
|
96
|
-
@post.
|
97
|
-
redirect PostN, @post
|
96
|
+
@post.update :title => input.post_title, :body => input.post_body
|
97
|
+
redirect PostN, @post
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
@@ -102,7 +102,7 @@ module Blog
|
|
102
102
|
def get
|
103
103
|
render :login
|
104
104
|
end
|
105
|
-
|
105
|
+
|
106
106
|
def post
|
107
107
|
@user = User.find_by_username_and_password(input.username, input.password)
|
108
108
|
|
@@ -112,7 +112,7 @@ module Blog
|
|
112
112
|
else
|
113
113
|
@info = 'Wrong username or password.'
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
render :login
|
117
117
|
end
|
118
118
|
end
|
@@ -128,17 +128,17 @@ module Blog
|
|
128
128
|
STYLE = File.read(__FILE__).gsub(/.*__END__/m, '')
|
129
129
|
|
130
130
|
def get
|
131
|
-
@headers['
|
131
|
+
@headers['content-type'] = 'text/css; charset=utf-8'
|
132
132
|
STYLE
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
136
|
-
|
136
|
+
|
137
137
|
module Helpers
|
138
138
|
def logged_in?
|
139
139
|
!!@state.user_id
|
140
140
|
end
|
141
|
-
|
141
|
+
|
142
142
|
def require_login!
|
143
143
|
unless logged_in?
|
144
144
|
redirect Controllers::Login
|
@@ -152,18 +152,18 @@ module Blog
|
|
152
152
|
html do
|
153
153
|
head do
|
154
154
|
title 'My Blog'
|
155
|
-
link :rel => 'stylesheet', :type => 'text/css',
|
155
|
+
link :rel => 'stylesheet', :type => 'text/css',
|
156
156
|
:href => '/styles.css', :media => 'screen'
|
157
157
|
end
|
158
158
|
body do
|
159
159
|
h1 { a 'My Blog', :href => R(Index) }
|
160
|
-
|
160
|
+
|
161
161
|
div.wrapper! do
|
162
|
-
|
162
|
+
self << yield
|
163
163
|
end
|
164
|
-
|
164
|
+
|
165
165
|
hr
|
166
|
-
|
166
|
+
|
167
167
|
p.footer! do
|
168
168
|
if logged_in?
|
169
169
|
_admin_menu
|
@@ -171,7 +171,7 @@ module Blog
|
|
171
171
|
a 'Login', :href => R(Login)
|
172
172
|
text ' to the adminpanel'
|
173
173
|
end
|
174
|
-
text ' – Powered by '
|
174
|
+
text! ' – Powered by '
|
175
175
|
a 'Camping', :href => 'http://camping.rubyforge.org/'
|
176
176
|
end
|
177
177
|
end
|
@@ -196,10 +196,10 @@ module Blog
|
|
196
196
|
def login
|
197
197
|
h2 'Login'
|
198
198
|
p.info @info if @info
|
199
|
-
|
199
|
+
|
200
200
|
form :action => R(Login), :method => 'post' do
|
201
201
|
input :name => 'to', :type => 'hidden', :value => @to if @to
|
202
|
-
|
202
|
+
|
203
203
|
label 'Username', :for => 'username'
|
204
204
|
input :name => 'username', :id => 'username', :type => 'text'
|
205
205
|
|
@@ -224,21 +224,21 @@ module Blog
|
|
224
224
|
|
225
225
|
# partials
|
226
226
|
def _admin_menu
|
227
|
-
text [['Log out', R(Logout)], ['New', R(PostNew)]].map { |name, to|
|
228
|
-
|
227
|
+
text! [['Log out', R(Logout)], ['New', R(PostNew)]].map { |name, to|
|
228
|
+
mab { a name, :href => to}
|
229
229
|
}.join(' – ')
|
230
230
|
end
|
231
231
|
|
232
232
|
def _post(post)
|
233
233
|
h2 { a post.title, :href => R(PostN, post) }
|
234
234
|
p.info do
|
235
|
-
text "Written by <strong>#{post.user.username}</strong> "
|
235
|
+
text! "Written by <strong>#{post.user.username}</strong> "
|
236
236
|
text post.updated_at.strftime('%B %M, %Y @ %H:%M ')
|
237
237
|
_post_menu(post)
|
238
238
|
end
|
239
|
-
text post.html_body
|
239
|
+
text! post.html_body
|
240
240
|
end
|
241
|
-
|
241
|
+
|
242
242
|
def _post_menu(post)
|
243
243
|
if logged_in?
|
244
244
|
a '(edit)', :href => R(Edit, post)
|
@@ -248,7 +248,7 @@ module Blog
|
|
248
248
|
def _form(post, opts)
|
249
249
|
form({:method => 'post'}.merge(opts)) do
|
250
250
|
label 'Title', :for => 'post_title'
|
251
|
-
input :name => 'post_title', :id => 'post_title', :type => 'text',
|
251
|
+
input :name => 'post_title', :id => 'post_title', :type => 'text',
|
252
252
|
:value => post.title
|
253
253
|
|
254
254
|
label 'Body', :for => 'post_body'
|
@@ -281,12 +281,12 @@ h1, h2, h3, h4 {
|
|
281
281
|
font-weight: normal;
|
282
282
|
}
|
283
283
|
|
284
|
-
h1 {
|
284
|
+
h1 {
|
285
285
|
background-color: #EEE;
|
286
286
|
border-bottom: 5px solid #6F812D;
|
287
|
-
outline: 5px solid #9CB441;
|
287
|
+
outline: 5px solid #9CB441;
|
288
288
|
font-weight: normal;
|
289
|
-
font-size: 3em;
|
289
|
+
font-size: 3em;
|
290
290
|
padding: 0.5em 0;
|
291
291
|
text-align: center;
|
292
292
|
}
|
@@ -301,7 +301,7 @@ h1 a:hover { color: #143D55; text-decoration: underline }
|
|
301
301
|
h2 a { color: #287AA9; text-decoration: none }
|
302
302
|
h2 a:hover { color: #287AA9; text-decoration: underline }
|
303
303
|
|
304
|
-
#wrapper {
|
304
|
+
#wrapper {
|
305
305
|
margin: 3em auto;
|
306
306
|
width: 700px;
|
307
307
|
}
|
@@ -333,29 +333,29 @@ a:hover {
|
|
333
333
|
|
334
334
|
hr {
|
335
335
|
border-width: 5px 0;
|
336
|
-
border-style: solid;
|
336
|
+
border-style: solid;
|
337
337
|
border-color: #9CB441;
|
338
338
|
border-bottom-color: #6F812D;
|
339
|
-
height: 0;
|
339
|
+
height: 0;
|
340
340
|
}
|
341
341
|
|
342
|
-
p#footer {
|
342
|
+
p#footer {
|
343
343
|
font-size: 0.9em;
|
344
|
-
margin: 0;
|
344
|
+
margin: 0;
|
345
345
|
padding: 1em;
|
346
346
|
text-align: center;
|
347
347
|
}
|
348
348
|
|
349
|
-
label {
|
349
|
+
label {
|
350
350
|
display: block;
|
351
351
|
width: 100%;
|
352
352
|
}
|
353
353
|
|
354
354
|
input, textarea {
|
355
|
-
padding: 5px;
|
355
|
+
padding: 5px;
|
356
356
|
margin-bottom: 1em;
|
357
357
|
margin-right: 490px;
|
358
|
-
width: 200px;
|
358
|
+
width: 200px;
|
359
359
|
}
|
360
360
|
|
361
361
|
input.submit {
|
data/lib/camping/ar.rb
CHANGED
@@ -9,13 +9,28 @@ end
|
|
9
9
|
$AR_EXTRAS = %{
|
10
10
|
Base = ActiveRecord::Base unless const_defined? :Base
|
11
11
|
|
12
|
+
class ActiveRecordCloser
|
13
|
+
def initialize(app)
|
14
|
+
@app = app
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
@app.call(env)
|
19
|
+
ensure
|
20
|
+
conn = ActiveRecord::Base.connection
|
21
|
+
conn.close if conn.respond_to?(:close)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Camping.use ActiveRecordCloser
|
26
|
+
|
12
27
|
class SchemaInfo < Base
|
13
28
|
end
|
14
29
|
|
15
30
|
def self.V(n)
|
16
31
|
@final = [n, @final.to_f].max
|
17
32
|
m = (@migrations ||= [])
|
18
|
-
Class.new(ActiveRecord::Migration) do
|
33
|
+
Class.new(ActiveRecord::Migration[6.1]) do
|
19
34
|
meta_def(:version) { n }
|
20
35
|
meta_def(:inherited) { |k| m << k }
|
21
36
|
end
|
@@ -33,13 +48,13 @@ $AR_EXTRAS = %{
|
|
33
48
|
end
|
34
49
|
end
|
35
50
|
|
36
|
-
si = SchemaInfo.
|
51
|
+
si = SchemaInfo.first || SchemaInfo.new(:version => opts[:assume])
|
37
52
|
if si.version < opts[:version]
|
38
53
|
@migrations.sort_by { |m| m.version }.each do |k|
|
39
54
|
k.migrate(:up) if si.version < k.version and k.version <= opts[:version]
|
40
55
|
k.migrate(:down) if si.version > k.version and k.version > opts[:version]
|
41
56
|
end
|
42
|
-
si.
|
57
|
+
si.update(:version => opts[:version])
|
43
58
|
end
|
44
59
|
end
|
45
60
|
end
|
@@ -69,7 +84,7 @@ module Camping
|
|
69
84
|
module_eval $AR_EXTRAS
|
70
85
|
end
|
71
86
|
end
|
72
|
-
Camping::S.sub!
|
87
|
+
Camping::S.sub!(/autoload\s*:Base\s*,\s*['"]camping\/ar['"]/, $AR_EXTRAS)
|
73
88
|
Camping::Apps.each do |c|
|
74
|
-
c::Models.module_eval $AR_EXTRAS
|
89
|
+
c::Models.module_eval $AR_EXTRAS.gsub('Camping', c.to_s)
|
75
90
|
end
|