sinatra-mvc 0.0.1
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.
- data/LICENSE +22 -0
- data/README.md +463 -0
- data/bin/sinatra-mvc +8 -0
- data/bin/sinatra-mvc-project +24 -0
- data/lib/sinatra-mvc.rb +44 -0
- data/lib/sinatra-mvc/conditional_form_field.rb +14 -0
- data/lib/sinatra-mvc/database_connection.rb +15 -0
- data/lib/sinatra-mvc/escaping.rb +7 -0
- data/lib/sinatra-mvc/flash_messages.rb +4 -0
- data/lib/sinatra-mvc/load_app.rb +12 -0
- data/lib/sinatra-mvc/load_utils.rb +13 -0
- data/lib/sinatra-mvc/post_handler.rb +67 -0
- data/lib/sinatra-mvc/render_params.rb +18 -0
- data/lib/sinatra-mvc/session_store.rb +13 -0
- data/lib/sinatra-mvc/settings.rb +29 -0
- data/lib/sinatra-mvc/view_prefix.rb +17 -0
- data/skel/.gitignore +4 -0
- data/skel/.hgignore +7 -0
- data/skel/Gemfile +17 -0
- data/skel/app/index.rb +3 -0
- data/skel/app/not_found.rb +5 -0
- data/skel/conf/environment.rb +10 -0
- data/skel/conf/settings.yml +18 -0
- data/skel/config.ru +8 -0
- data/skel/i18n/en.yml +5 -0
- data/skel/models/.dir +0 -0
- data/skel/public/.dir +0 -0
- data/skel/utils/initdb.rb +12 -0
- data/skel/utils/upgradedb.rb +3 -0
- data/skel/views/docs.md +463 -0
- data/skel/views/flash_message.erubis +19 -0
- data/skel/views/index.erubis +7 -0
- data/skel/views/layout.erubis +15 -0
- data/skel/views/not_found.erubis +3 -0
- metadata +281 -0
data/bin/sinatra-mvc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby1.9.1
|
2
|
+
# Creates a new project.
|
3
|
+
|
4
|
+
require 'fileutils'
|
5
|
+
require 'rubygems'
|
6
|
+
|
7
|
+
unless ARGV[0]
|
8
|
+
$stderr.puts 'Please supply the name of the new project'
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
|
12
|
+
skel = File.join File.dirname(Gem.bin_path('sinatra-mvc')), '..', 'skel'
|
13
|
+
|
14
|
+
begin
|
15
|
+
FileUtils.cp_r skel, ARGV[0]
|
16
|
+
Dir.chdir ARGV[0] do
|
17
|
+
system 'bundle install --path vendor --binstubs'
|
18
|
+
end
|
19
|
+
rescue
|
20
|
+
$stderr.puts $!
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "Finished. Your project is located in %s." % File.expand_path(ARGV[0])
|
data/lib/sinatra-mvc.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Sinatra MVC Web Application
|
2
|
+
# Based on Sinatra and Common Sense (tm)
|
3
|
+
#
|
4
|
+
# Joris van Rooij <jorrizza@jrrzz.net>
|
5
|
+
# http://www.jrrzz.net/
|
6
|
+
|
7
|
+
# Check if we've got a PROJECT defined.
|
8
|
+
raise RuntimeError, 'PROJECT is not defined!' unless defined? PROJECT
|
9
|
+
|
10
|
+
# A hack for now, but at least it's better than nothing.
|
11
|
+
Encoding.default_external = 'UTF-8'
|
12
|
+
|
13
|
+
# Project include path.
|
14
|
+
$:.push PROJECT
|
15
|
+
|
16
|
+
# Guess what. We need these.
|
17
|
+
require 'rubygems'
|
18
|
+
require 'sinatra'
|
19
|
+
require 'erubis'
|
20
|
+
|
21
|
+
# i18n using R18n.
|
22
|
+
require 'sinatra/r18n'
|
23
|
+
|
24
|
+
# Load all of the core modules, in order.
|
25
|
+
require 'sinatra-mvc/settings'
|
26
|
+
require 'sinatra-mvc/view_prefix'
|
27
|
+
require 'sinatra-mvc/render_params'
|
28
|
+
require 'sinatra-mvc/database_connection'
|
29
|
+
require 'sinatra-mvc/session_store'
|
30
|
+
require 'sinatra-mvc/flash_messages'
|
31
|
+
require 'sinatra-mvc/post_handler'
|
32
|
+
require 'sinatra-mvc/conditional_form_field'
|
33
|
+
require 'sinatra-mvc/escaping'
|
34
|
+
|
35
|
+
# We use Bundler to manage the rest of the deps.
|
36
|
+
require 'bundler/setup'
|
37
|
+
|
38
|
+
# Load the application.
|
39
|
+
require 'conf/environment'
|
40
|
+
require 'sinatra-mvc/load_app'
|
41
|
+
require 'sinatra-mvc/load_utils'
|
42
|
+
|
43
|
+
# Start the classic mode.
|
44
|
+
set :run, true
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Simple helper to display form field data when available.
|
2
|
+
# It prefers params, but when unavailable it'll use the supplied object (if available).
|
3
|
+
|
4
|
+
helpers do
|
5
|
+
def c(field, object = nil)
|
6
|
+
if params.has_key? field.to_s
|
7
|
+
h params[field.to_s]
|
8
|
+
elsif object.respond_to? field
|
9
|
+
h object.send(field)
|
10
|
+
else
|
11
|
+
""
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'dm-types'
|
3
|
+
require 'dm-validations'
|
4
|
+
|
5
|
+
# Add translations to models
|
6
|
+
require 'r18n-core/translated'
|
7
|
+
|
8
|
+
# When we're developing, some DataMapper debug would be nice.
|
9
|
+
if development?
|
10
|
+
DataMapper::Logger.new $stdout, :debug
|
11
|
+
#DataMapper::Model.raise_on_save_failure = true
|
12
|
+
end
|
13
|
+
|
14
|
+
# Set up our database connection.
|
15
|
+
DataMapper.setup :default, Settings.settings['database_connection']
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Loads the app recursively.
|
2
|
+
# It does this by first loading all of the models and then
|
3
|
+
# the controllers (app components if you will).
|
4
|
+
|
5
|
+
def load_app(dir)
|
6
|
+
Dir.glob(File.join PROJECT, dir, '**', '*.rb').sort.each do |file|
|
7
|
+
load file
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
load_app 'models'
|
12
|
+
load_app 'app'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Loads the proper utility from the utils directory
|
2
|
+
# when that utility is called as a command line option.
|
3
|
+
|
4
|
+
if ARGV[0] && !defined? RACKUP
|
5
|
+
util = File.join(PROJECT, 'utils', "#{ARGV[0]}.rb")
|
6
|
+
if File.exists? util
|
7
|
+
printf "Running util %s.\n", ARGV[0]
|
8
|
+
require util
|
9
|
+
exit
|
10
|
+
else
|
11
|
+
printf ">> Util %s does not exist. Continue as normal.\n", ARGV[0]
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Utility functions to handle POST data.
|
2
|
+
# TODO: Write post_object_multi for field[] input fields
|
3
|
+
|
4
|
+
helpers do
|
5
|
+
# Makes fetch more readable
|
6
|
+
def n
|
7
|
+
:n
|
8
|
+
end
|
9
|
+
|
10
|
+
# Create or modify a single object from POST data.
|
11
|
+
# We assume the programmer isn't stupid, and actually provided a DataMapper
|
12
|
+
# class or object as the first argument.
|
13
|
+
# The second and third arguments supply a redirection mechanism on succes
|
14
|
+
# and failure. By default it's the referer. When nil, no redirection will
|
15
|
+
# take place.
|
16
|
+
def fetch(mode, input,
|
17
|
+
the_way_forward = request.env['HTTP_REFERER'],
|
18
|
+
the_way_back = request.env['HTTP_REFERER'])
|
19
|
+
|
20
|
+
# Get the proper object and class.
|
21
|
+
if input.is_a? DataMapper::Resource
|
22
|
+
the_object = input
|
23
|
+
the_class = the_object.class
|
24
|
+
else
|
25
|
+
the_class = input
|
26
|
+
the_object = the_class.new
|
27
|
+
end
|
28
|
+
|
29
|
+
# fetch 1, ...
|
30
|
+
if mode == 1
|
31
|
+
# Check for each parameter if we've got a model field
|
32
|
+
# and call the setter for that field and redirect.
|
33
|
+
params.each do |field, value|
|
34
|
+
method = (field + '=').to_sym
|
35
|
+
if the_object.respond_to? method
|
36
|
+
the_object.send(method, value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# If the object's valid, save and redirect. If not, show all
|
41
|
+
# the errors to the user at the way back location. We also make
|
42
|
+
# sure the same error doesn't show twice.
|
43
|
+
if the_object.valid?
|
44
|
+
the_object.save
|
45
|
+
redirect the_way_forward unless the_way_forward.nil?
|
46
|
+
else
|
47
|
+
flash[:error] = []
|
48
|
+
found_errors = []
|
49
|
+
the_object.errors.each_pair do |field, error|
|
50
|
+
field = field.to_s
|
51
|
+
field.gsub! /_id$/, ''
|
52
|
+
error.each do |e|
|
53
|
+
unless found_errors.include? [field, error]
|
54
|
+
flash[:error] << t[the_class.inspect.downcase.to_sym][field.to_sym] + ': ' + e
|
55
|
+
found_errors << [field, error]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
redirect the_way_back unless the_way_back.nil?
|
60
|
+
end
|
61
|
+
elsif mode == :n
|
62
|
+
raise NotImplementedError, 'Sorry, fetch n, ... is not implemented yet.'
|
63
|
+
else
|
64
|
+
raise ArgumentError, 'No such mode: ' + mode.inspect
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# This will enable neat parameters like Rails and PHP have.
|
2
|
+
# You know, the variable[key] things.
|
3
|
+
# From the Sinatra book.
|
4
|
+
|
5
|
+
before do
|
6
|
+
new_params = {}
|
7
|
+
params.each_pair do |full_key, value|
|
8
|
+
this_param = new_params
|
9
|
+
split_keys = full_key.split(/\]\[|\]|\[/)
|
10
|
+
split_keys.each_index do |index|
|
11
|
+
break if split_keys.length == index + 1
|
12
|
+
this_param[split_keys[index]] ||= {}
|
13
|
+
this_param = this_param[split_keys[index]]
|
14
|
+
end
|
15
|
+
this_param[split_keys.last] = value
|
16
|
+
end
|
17
|
+
request.params.replace new_params
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Sessions for Sinatra
|
2
|
+
# Both cookie based as Memcache based sessions are supported
|
3
|
+
|
4
|
+
case settings.session_backend
|
5
|
+
when :cookie
|
6
|
+
use Rack::Session::Cookie, :expire_after => settings.session_max_age, :key => 'sinatra.mvc.session', :secret => (0..50).map do
|
7
|
+
65 + rand(25).chr
|
8
|
+
end.join
|
9
|
+
when :memcache
|
10
|
+
use Rack::Session::Memcache, :expire_after => settings.session_max_age, :key => 'sinatra.mvc.session', :namespace => 'sinatra:mvc:session', :memcache_server => settings.session_store
|
11
|
+
else
|
12
|
+
raise 'Unknown session backend: ' + settings.session_backend.inspect
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Loads the settings
|
2
|
+
|
3
|
+
require 'psych'
|
4
|
+
|
5
|
+
class Settings
|
6
|
+
def self.load(filename)
|
7
|
+
begin
|
8
|
+
@@settings = Psych.load_file filename
|
9
|
+
rescue
|
10
|
+
raise 'Could not load configuration! Psych said: ' + $!.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.settings
|
15
|
+
@@settings
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Settings.load File.join(PROJECT, 'conf', 'settings.yml')
|
20
|
+
|
21
|
+
# Now we have to feed the Sinatra settings
|
22
|
+
Settings.settings.each_pair do |setting, value|
|
23
|
+
case setting
|
24
|
+
when 'views_root', 'translations', 'public'
|
25
|
+
set setting.to_sym, File.join(PROJECT, value)
|
26
|
+
else
|
27
|
+
set setting.to_sym, value
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Set the view prefix right if the
|
2
|
+
# first part of the URL exists as a subdir
|
3
|
+
# of our main view directory.
|
4
|
+
|
5
|
+
before do
|
6
|
+
first_dir = request.env['REQUEST_URI'].split('/')[1]
|
7
|
+
|
8
|
+
if first_dir
|
9
|
+
if File.directory? File.join(settings.views_root, first_dir)
|
10
|
+
set :views, File.join(settings.views_root, first_dir)
|
11
|
+
else
|
12
|
+
set :views, settings.views_root
|
13
|
+
end
|
14
|
+
else
|
15
|
+
set :views, settings.views_root
|
16
|
+
end
|
17
|
+
end
|
data/skel/.gitignore
ADDED
data/skel/.hgignore
ADDED
data/skel/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source :rubygems
|
2
|
+
|
3
|
+
# A faster web server.
|
4
|
+
gem 'thin'
|
5
|
+
|
6
|
+
# Markdown rendering support for R18n and Tilt.
|
7
|
+
gem 'rdiscount'
|
8
|
+
gem 'maruku'
|
9
|
+
|
10
|
+
# Datamapper is nowhere without this.
|
11
|
+
gem 'dm-migrations'
|
12
|
+
gem 'dm-aggregates'
|
13
|
+
|
14
|
+
# Some often used template utilities. Add/enable at will.
|
15
|
+
# gem 'RedCloth'
|
16
|
+
# gem 'liquid'
|
17
|
+
# gem 'less'
|
data/skel/app/index.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
# Sessions
|
3
|
+
session_max_age: 86400
|
4
|
+
session_backend: :memcache
|
5
|
+
session_store: localhost:11211
|
6
|
+
|
7
|
+
# Views
|
8
|
+
views_root: views
|
9
|
+
|
10
|
+
# Public directory
|
11
|
+
public: public
|
12
|
+
|
13
|
+
# i18n
|
14
|
+
default_locale: en
|
15
|
+
translations: i18n
|
16
|
+
|
17
|
+
# Database
|
18
|
+
database_connection: mysql://sinatra:sinatra@localhost/sinatra_mvc
|
data/skel/config.ru
ADDED
data/skel/i18n/en.yml
ADDED
data/skel/models/.dir
ADDED
File without changes
|
data/skel/public/.dir
ADDED
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Initializes the database if asked to.
|
2
|
+
|
3
|
+
# Are you sure?
|
4
|
+
print "\nWarning, this will DESTROY EVERYTHING YOU HAVE!\nAnd probably more...\n\nAre you sure? (y/N)"
|
5
|
+
STDOUT.flush
|
6
|
+
answer = STDIN.gets
|
7
|
+
exit unless answer == "y\n"
|
8
|
+
|
9
|
+
# Rebuild the database structure.
|
10
|
+
DataMapper.auto_migrate!
|
11
|
+
|
12
|
+
# Add custom things to do after an init here
|
data/skel/views/docs.md
ADDED
@@ -0,0 +1,463 @@
|
|
1
|
+
Sinatra MVC
|
2
|
+
===========
|
3
|
+
|
4
|
+
Sinatra MVC is a simple attempt to get some kind of MVC structure on top
|
5
|
+
of Sinatra, without losing much of the original Sinatra _"feeling"_. It
|
6
|
+
uses Datamapper for it's model layer and custom software for the other
|
7
|
+
two.
|
8
|
+
|
9
|
+
It's recommended to read the [Sinatra README][6] first before continuing
|
10
|
+
with this document.
|
11
|
+
|
12
|
+
A rule of thumb: In command line examples, a `$` prefix means your own
|
13
|
+
user and a `#` prefix is a root terminal.
|
14
|
+
|
15
|
+
System Dependencies
|
16
|
+
-------------------
|
17
|
+
|
18
|
+
Your system needs to have a working Ruby 1.9.2 installation (or later,
|
19
|
+
but I haven't tested that). You'll also need some kind of database. The
|
20
|
+
currently supported databases are:
|
21
|
+
|
22
|
+
* MySQL
|
23
|
+
* PostgreSQL
|
24
|
+
* Sqlite3
|
25
|
+
|
26
|
+
Sinatra MVC also has the possibility to use Memcache as a session storage
|
27
|
+
system. This is the default. It's recommended as well.
|
28
|
+
|
29
|
+
The framework has been developed on a Debian Sid platform, and as such
|
30
|
+
Debian is the preferred platform of choice. If you encounter problems
|
31
|
+
caused by Debian-specific hacks, please let me know. The interpreter
|
32
|
+
string has been set to `ruby1.9.1`, but you can easily change that in
|
33
|
+
the `bin` directory if you wish. Don't worry, that's the only place the
|
34
|
+
"weird" interpreter string is used.
|
35
|
+
|
36
|
+
Throughout the documentation Debian-specific help will be provided. Other
|
37
|
+
operating systems might be added later.
|
38
|
+
|
39
|
+
Installing
|
40
|
+
----------
|
41
|
+
|
42
|
+
Installing Sinatra MVC is reasonably simple. All you need is Mercurial,
|
43
|
+
some development headers (distributed by your operating system) and
|
44
|
+
a terminal.
|
45
|
+
|
46
|
+
For Debian users, the following command will suffice (or ask your system
|
47
|
+
administrator to install the packages for you):
|
48
|
+
|
49
|
+
# apt-get install ruby1.9.1-full libmysqlclient-dev libpq-dev libsqlite3-dev build-essential
|
50
|
+
|
51
|
+
You'll have to make sure the Ruby gem path is in your terminal's `$PATH`.
|
52
|
+
For Debian, adding the following line to your `~/.bashrc` will do just
|
53
|
+
fine. Don't forget to restart your shell to enable this!
|
54
|
+
|
55
|
+
PATH="/var/lib/gems/1.9.1/bin/:$PATH"
|
56
|
+
|
57
|
+
The simplest method is using Rubygems. For Debian, use `gem1.9.1` instead
|
58
|
+
of `gem`.
|
59
|
+
|
60
|
+
# gem install sinatra-mvc
|
61
|
+
|
62
|
+
Or for the latest and greatest, you can need to download the source tree.
|
63
|
+
It's available on both [Github][8] and [Bitbucket][9], but both are only
|
64
|
+
mirrors of the development tree at wasda.nl.
|
65
|
+
|
66
|
+
$ cd $HOME/src
|
67
|
+
$ hg clone https://bitbucket.org/jorrizza/sinatra-mvc
|
68
|
+
- or if you prefer github -
|
69
|
+
$ git clone git://github.com/jorrizza/sinatra-mvc.git
|
70
|
+
$ cd sinatra-mvc
|
71
|
+
$ rm sinatra-mvc-*.gem
|
72
|
+
$ gem build sinatra-mvc.gemspec
|
73
|
+
# gem install sinatra-mvc-*.gem
|
74
|
+
|
75
|
+
Now we've got sinatra-mvc installed, let's start our own project.
|
76
|
+
|
77
|
+
$ cd $HOME/src
|
78
|
+
$ sinatra-mvc-project my_project
|
79
|
+
$ cd my_project
|
80
|
+
|
81
|
+
Yay! A project!
|
82
|
+
|
83
|
+
Using bundler, we can install all of our gems without getting in the way of
|
84
|
+
your host Ruby installation. The `sinatra-mvc-project` utility has already
|
85
|
+
installed the bundler files in your project with the default set of gems.
|
86
|
+
|
87
|
+
Updating gems is pretty easily done. Now you've got your bundle complete,
|
88
|
+
you'll just have to run:
|
89
|
+
|
90
|
+
$ bundle update
|
91
|
+
|
92
|
+
When you need more gems to be added to your project, simply edit the
|
93
|
+
`Gemfile` and run
|
94
|
+
|
95
|
+
$ bundle update
|
96
|
+
|
97
|
+
again. This will make sure the dependencies of your application, as supplied
|
98
|
+
in the `Gemfile`, will be available to your project. For further
|
99
|
+
documentation about the `Gemfile`, read the [Bundler documentation about
|
100
|
+
the `Gemfile`][17]
|
101
|
+
|
102
|
+
Updating
|
103
|
+
--------
|
104
|
+
|
105
|
+
For a Rubygems installation simply run:
|
106
|
+
|
107
|
+
# gem update
|
108
|
+
|
109
|
+
To get the latest updates from the repository, just pull (and merge if needed).
|
110
|
+
|
111
|
+
$ cd $HOME/src/sinatra-mvc
|
112
|
+
$ hg pull
|
113
|
+
$ hg update
|
114
|
+
- or when using github -
|
115
|
+
$ git pull
|
116
|
+
$ gem build sinatra-mvc.gemspec
|
117
|
+
# gem install sinatra-mvc-0.0.1.gem
|
118
|
+
|
119
|
+
Configuration
|
120
|
+
-------------
|
121
|
+
|
122
|
+
The main configuration is defined in `conf/settings.yml`. It's the place
|
123
|
+
where you can use the Sinatra `set` method to change Sinatra's behaviour.
|
124
|
+
Nothing keeps you from setting configuration parameters in controllers,
|
125
|
+
but please keep things nicely tucked away in this file. Every field will
|
126
|
+
be translated to a `set :field, value` call.
|
127
|
+
|
128
|
+
For sessions there are three configuration parameters you can set. The
|
129
|
+
`session_max_age` determines the age of the session cookie in seconds.
|
130
|
+
After this amount of time, the cookie is denied and browsers should
|
131
|
+
automatically discard it. There are two session backends you can choose
|
132
|
+
from. If you set `session_backend` to `:cookie`, all the session values
|
133
|
+
will be stored in the cookie itself. Even though it will be encrypted,
|
134
|
+
this is not a very safe thing to do. It also limits your storage to the
|
135
|
+
maximum allowed cookie size, which varies from browser to browser. The
|
136
|
+
preferred setting is `:memcache`, which will use memcache as a session
|
137
|
+
backend. It doesn't limit your session size that much, and can scale
|
138
|
+
pretty well. Set the `session_store` to either a string or an array of
|
139
|
+
strings for a single server or a memcached cluster. The format is
|
140
|
+
`hostname:port`. This value will be ignored for the `:cookie`
|
141
|
+
`session_backend` setting. So for a single memcache server you define:
|
142
|
+
|
143
|
+
session_store: hostname:11211
|
144
|
+
|
145
|
+
And for a memcache cluster:
|
146
|
+
|
147
|
+
session_store:
|
148
|
+
- hostname1:11211
|
149
|
+
- hostname2:11211
|
150
|
+
- hostname3:11211
|
151
|
+
|
152
|
+
If you want to change the path to the views root directory, you can change
|
153
|
+
the `views_root` setting. It's `views` by default. This is interpreted as
|
154
|
+
a subdirectory of your project.
|
155
|
+
|
156
|
+
The same applies to the `public` setting, which should point to the
|
157
|
+
directory within your project from which static content is being served.
|
158
|
+
|
159
|
+
For i18n you can set the default locale using `default_locale`. This is
|
160
|
+
the name of the file in the `translations` directory, without the `.yml`
|
161
|
+
file extension. Just like `views_root`, `translations` is a subdirectory
|
162
|
+
of your project.
|
163
|
+
|
164
|
+
The database connection is defined by `database_connection`. The value is
|
165
|
+
a string, following the syntax:
|
166
|
+
|
167
|
+
* `sqlite::memory:` for in-memory Sqlite3 storage
|
168
|
+
* `sqlite:///path/to/file.db` for file-based Sqlite3
|
169
|
+
* `mysql://user:pass@server/database` for the MySQL RDBMS
|
170
|
+
* `postgres://user:pass@server/database` for the PostgreSQL RDBMS
|
171
|
+
|
172
|
+
You can read the settings file using the `Settings.settings` call, which
|
173
|
+
will return a Hash of your settings. Alternatively you can read the
|
174
|
+
configuration Sinatra received by using the `settings` object like explained
|
175
|
+
in the [Sinatra settings documentation][15]. These two differ slightly,
|
176
|
+
mainly in the fact that Sinatra isn't aware of the project directory.
|
177
|
+
|
178
|
+
Running your Application
|
179
|
+
------------------------
|
180
|
+
|
181
|
+
Sinatra is built on top of Rack, so every method that can run Rack will
|
182
|
+
be able to run your Sinatra MVC application. That even includes things
|
183
|
+
like [Shotgun][2], [Phusion Passenger][3] and [Heroku][4].
|
184
|
+
|
185
|
+
There are basically two ways to run your application. During development,
|
186
|
+
it's okay to run your application using the built-in thin server. This
|
187
|
+
will serve all the static files and handle the application calls at the
|
188
|
+
same time. Just simply run:
|
189
|
+
|
190
|
+
$ cd my_project
|
191
|
+
$ sinatra-mvc
|
192
|
+
|
193
|
+
This will run your application in development mode, allowing you to see
|
194
|
+
the access log in the terminal and tracebacks when you've made an _oops_.
|
195
|
+
It also enables nicely formatted error pages, generated by Sinatra.
|
196
|
+
|
197
|
+
Another method is using the `PROJECT` environment variable.
|
198
|
+
|
199
|
+
$ PROJECT=~/src/my_project sinatra-mvc
|
200
|
+
|
201
|
+
In production, there are several ways you can use Rack to serve your app.
|
202
|
+
I suggest using thin, proxied by Nginx for the static files. The
|
203
|
+
supplied `config.ru` Rackup file will handle things for you. Be sure to
|
204
|
+
configure your server to run in production mode. This will disable the
|
205
|
+
helpful error messages and other development coolness.
|
206
|
+
|
207
|
+
An example using Shotgun:
|
208
|
+
|
209
|
+
# gem1.9.1 install shotgun
|
210
|
+
$ shotgun ~/src/my_project/config.ru
|
211
|
+
|
212
|
+
Or:
|
213
|
+
|
214
|
+
$ cd ~/src/my_project
|
215
|
+
$ shotgun
|
216
|
+
|
217
|
+
Static Files
|
218
|
+
------------
|
219
|
+
|
220
|
+
By default, static files are served from the `public/` directory. If you
|
221
|
+
create a file at `public/css/site/main.css`, the HTTP request to
|
222
|
+
`/public/css/site/main.css` will serve that file. You're completely free
|
223
|
+
to specify your own directory structure.
|
224
|
+
|
225
|
+
Controllers
|
226
|
+
-----------
|
227
|
+
|
228
|
+
Controllers are vastly simplified and are not at all linked to models.
|
229
|
+
If you want to make it so, you're free to do so. The controller files
|
230
|
+
reside under `app/`. All of the files are read recursively in order during
|
231
|
+
application startup. This means you can apply a sane directory structure
|
232
|
+
to the app directory to make your controllers easier to understand. Since
|
233
|
+
only the application's startup time is slightly influenced by the
|
234
|
+
complexity of the directory structure and the amount of files in them,
|
235
|
+
you're encouraged to split up your controllers as much as needed.
|
236
|
+
|
237
|
+
The code that goes into these files ends up in Sinatra's [application
|
238
|
+
scope][5]. You can fully use Sinatra's DSL to get things done. To keep
|
239
|
+
the original Sinatra _vibe_ alive, there's no central routing method.
|
240
|
+
Instead, you're required to use Sinatra's DSL to specify what happens
|
241
|
+
after what request.
|
242
|
+
|
243
|
+
Let's assume you've got a blog with posts, and you want to edit a certain
|
244
|
+
post. In this case, you might choose for the following file:
|
245
|
+
`app/post/modify.rb`.
|
246
|
+
|
247
|
+
get '/post/modify/:id'
|
248
|
+
@post = Post.get id
|
249
|
+
halt 404 unless @post
|
250
|
+
|
251
|
+
erubis :post_modify
|
252
|
+
end
|
253
|
+
|
254
|
+
post '/post/modify/:id'
|
255
|
+
@post = Post.get id
|
256
|
+
halt 404 unless @post
|
257
|
+
|
258
|
+
fetch 1, @post, '/post/read/' + id, nil
|
259
|
+
|
260
|
+
erubis :post_modify
|
261
|
+
end
|
262
|
+
|
263
|
+
As you can see, not much has been changed from the original concept.
|
264
|
+
The post itself is a Datamapper model, and is used a such.
|
265
|
+
|
266
|
+
In the post bit of the controller there's an awesome little function call,
|
267
|
+
allowing you to populate your model with incoming POST data. The `fetch`
|
268
|
+
function is designed to tackle most, but not all, handling of incoming
|
269
|
+
request data. It's built on top op Datamapper and expects either an object
|
270
|
+
or a class of a Datamapper model as it's second parameter. The spec:
|
271
|
+
|
272
|
+
fetch [1|n], [object|class], url_on_success = referer, url_on_failure = referer
|
273
|
+
|
274
|
+
The first parameter (`1` or `n`) switches functionality between fetching
|
275
|
+
a single object, or multiple objects of the same type. _Only single
|
276
|
+
object fetching is supported for now_. The second argument accepts both
|
277
|
+
classes and objects of Datamapper models. If it's a class, it will create
|
278
|
+
a new object using the received values and saves it to the database. If it's
|
279
|
+
an existing object, it will modify the object using the received fields.
|
280
|
+
Be sure to have your form field name attributes match the Datamapper field
|
281
|
+
names. If you have any other fields that end up in the `params` hash, make
|
282
|
+
sure the fields do not overlap. Both URL filters (like `get '/my/:id' do
|
283
|
+
... end`) and normal HTTP GET parameters (like `/my/?id=1`) interfere with
|
284
|
+
the `params` variable `fetch` uses. Internally the function will handle
|
285
|
+
Datamapper validation and will only write to the database when everything
|
286
|
+
checks out. The errors will be displayed using flash messages after a
|
287
|
+
redirect. That's what the last two parameters are for. Both are optional.
|
288
|
+
When excluded, they'll have the value of the current referer. The first
|
289
|
+
is the redirect on success URL. This will keep the back button from
|
290
|
+
resending the POST data. The second one is the redirect on failure URL.
|
291
|
+
When nil is given, no redirection will take place and control will be given
|
292
|
+
back to the controller, allowing you to have the conditional form fields
|
293
|
+
(`c` function) in your view display the sent data.
|
294
|
+
|
295
|
+
The flash messages aren't only available to the `fetch` function, but
|
296
|
+
you're allowed to set them yourself as well. There are two methods of using
|
297
|
+
the flash messages. You can alter the `flash` variable itself. The variable
|
298
|
+
is a hash with two possible fields. `:info` contains information, in either
|
299
|
+
and array or a string. `:error` contains the same, but for errors. If you
|
300
|
+
set the flash message, the first view that is rendered will display the
|
301
|
+
values. Take a look at `views/flash_message.erubis` for the markup. The
|
302
|
+
second method uses an extention to the `redirect` function, allowing you
|
303
|
+
to supply a message to be displayed after a redirect. Example:
|
304
|
+
|
305
|
+
redirect '/naked/horses', :info => 'Stand the f*ck back!'
|
306
|
+
|
307
|
+
Or more messages:
|
308
|
+
|
309
|
+
redirect '/naked/penguins', :info => ['This is unfunny', 'This kind of turns me on']
|
310
|
+
|
311
|
+
Or several types:
|
312
|
+
|
313
|
+
redirect '/naked/cows', :flash => {:info => 'Horny, get it?', :error => 'Not funny!'}
|
314
|
+
|
315
|
+
Views
|
316
|
+
-----
|
317
|
+
|
318
|
+
Sinatra MVC has all the [view options][1] Sinatra has. Some things differ
|
319
|
+
though, since this framework supports an URL-directory mapping for views.
|
320
|
+
|
321
|
+
Using _erubis_ is recommended, but you might as well use other templating
|
322
|
+
methods. Any template method supported by the tilt library, included by
|
323
|
+
Sinatra, can be used. Just make sure you've added the library to the
|
324
|
+
`Gemfile` and included it in the `conf/environment.rb`.
|
325
|
+
|
326
|
+
Some sidemarks with this selection of templating solutions:
|
327
|
+
|
328
|
+
* You can use less. Sinatra MVC wants to keep things speedy, so please use
|
329
|
+
`bin/lessc` to compile your less templates. Unless you've got a proper
|
330
|
+
cache of course.
|
331
|
+
* Markdown support in R18n is done using Maruku, but Sinatra (tilt) prefers
|
332
|
+
rdiscount. Both are included in the default `Gemfile`. One of the future
|
333
|
+
things that will be done is removing one of the two. This will have to do
|
334
|
+
for now.
|
335
|
+
|
336
|
+
Normally, you have to do weird stuff in Sinatra like using
|
337
|
+
`:'directory/my_view.erubis'` for rendering views in sub directories. Sinatra
|
338
|
+
MVC has added automatic view prefixes. The former method of using hardcoded
|
339
|
+
prefixes still works, but now there's URI-based mapping as well. In short,
|
340
|
+
it uses the views from the directory path in the view directory if that
|
341
|
+
path matches the URI prefix. For example, if you have a controller like
|
342
|
+
this:
|
343
|
+
|
344
|
+
get '/my/little/pony/pink'
|
345
|
+
erubis :pink
|
346
|
+
end
|
347
|
+
|
348
|
+
It will render a page using `views/my/little/pony/pink.erubis`, but only
|
349
|
+
if that directory exists. This directory will be used as the new view
|
350
|
+
prefix, so make sure every directory has at least the following files:
|
351
|
+
|
352
|
+
* `layout.erubis`
|
353
|
+
* `not_found.erubis`
|
354
|
+
* `flash_message.erubis` (only if layout requires it)
|
355
|
+
|
356
|
+
This construction allows you to create prefix-based sub sites, each with
|
357
|
+
it's own look and feel. Originally this has been created to allow for
|
358
|
+
admin areas and the like.
|
359
|
+
|
360
|
+
Views have a neat little function for displaying form values. It's called
|
361
|
+
conditional form field (`c` for short). The `c` function will take two
|
362
|
+
parameters, here's the spec:
|
363
|
+
|
364
|
+
c field, object = nil
|
365
|
+
|
366
|
+
The field is a symbol of the field from your Datamapper model. This
|
367
|
+
function will check if your field is found in POST data, and will display
|
368
|
+
that value. If it's not, it will return an empty string. Unless you've
|
369
|
+
supplied the second parameter, which is a Datamapper object. If the
|
370
|
+
object contains a value for that field, that will be displayed in stead
|
371
|
+
of an empty string. Here's a practical example for the `c` function for
|
372
|
+
handling a post's content.
|
373
|
+
|
374
|
+
<td><textarea name="content"><%=c :content, @post %></textarea></td>
|
375
|
+
|
376
|
+
The `c` function will automatically call `h` when creating output. The
|
377
|
+
`h` function escapes HTML, just like in Rails.
|
378
|
+
|
379
|
+
Models
|
380
|
+
------
|
381
|
+
|
382
|
+
Sinatra MVC uses Datamapper for it's models. Just like the controllers,
|
383
|
+
the models are included recursively so you are allowed to create your own
|
384
|
+
structure in the `models` directory.
|
385
|
+
|
386
|
+
For documentation regarding Datamapper, please visit de [Datamapper
|
387
|
+
documentation][7]. Some popular plugins are provided:
|
388
|
+
|
389
|
+
* [dm-migrations][12]: Adds migration support. Used by provided utils.
|
390
|
+
* [dm-aggregates][13]: Adds aggregation support (COUNT() and the like).
|
391
|
+
* [dm-validations][14]: Adds validation. Used extensively.
|
392
|
+
|
393
|
+
If you want to add more `dm-*` modules, just add them to your `Gemfile`
|
394
|
+
and include them in the `conf/environment.rb` file.
|
395
|
+
|
396
|
+
The classed defined in the models are automatically available in the
|
397
|
+
controllers.
|
398
|
+
|
399
|
+
When you've created your models, you can check and initialize them by
|
400
|
+
running:
|
401
|
+
|
402
|
+
$ cd my_project
|
403
|
+
$ sinatra-mvc initdb
|
404
|
+
|
405
|
+
This will initialize your database, but beware, it'll purge every model
|
406
|
+
defined in your `models` directory. If you just want to migrate your models
|
407
|
+
(e.g. update the database to reflect your models), just run:
|
408
|
+
|
409
|
+
$ cd my_project
|
410
|
+
$ sinatra-mvc upgradedb
|
411
|
+
|
412
|
+
This will only update the tables in such a way it can't modify any of the
|
413
|
+
data already present. To do that, you'll have to write migrations. This
|
414
|
+
functionality is lacking at the moment. Datamapper is able to run migrations,
|
415
|
+
but nobody bothered documenting how they work.
|
416
|
+
|
417
|
+
Internationalisation
|
418
|
+
--------------------
|
419
|
+
|
420
|
+
Internationalisation is done using R18n. This method allows for neat
|
421
|
+
integration into Sinatra. The documentation is complete and available on
|
422
|
+
[their site][16].
|
423
|
+
|
424
|
+
Utilities
|
425
|
+
---------
|
426
|
+
|
427
|
+
Utilities are scripts you can run within the Sinatra MVC environment. It's
|
428
|
+
pretty much what Rails does using `rake`, but without the complexity. Just
|
429
|
+
add a file in the `utils` directory and do whatever you want to do.
|
430
|
+
You can use the database like you're used to. The utils are meant to
|
431
|
+
function as cron jobs or management processes. As you can see, the database
|
432
|
+
scripts are already provided.
|
433
|
+
|
434
|
+
To run a script, simply call:
|
435
|
+
|
436
|
+
$ cd my_project
|
437
|
+
$ sinatra-mvc <scriptname without .rb>
|
438
|
+
|
439
|
+
Single Character Reserved Variables
|
440
|
+
-----------------------------------
|
441
|
+
|
442
|
+
Just don't use these as variables within controllers and views, mkay?
|
443
|
+
|
444
|
+
* `h - ` HTML escaping function.
|
445
|
+
* `t - ` Translation function (R18n).
|
446
|
+
* `c - ` Conditional form field.
|
447
|
+
* `n - ` Just meaning "n" of something.
|
448
|
+
|
449
|
+
[1]: http://rubydoc.info/gems/sinatra/1.1.0/file/README.rdoc#Views___Templates
|
450
|
+
[2]: http://rtomayko.github.com/shotgun/
|
451
|
+
[3]: http://www.modrails.com/
|
452
|
+
[4]: http://heroku.com/
|
453
|
+
[5]: http://www.rubydoc.info/gems/sinatra/1.1.0/file/README.rdoc#Application_Class_Scope
|
454
|
+
[6]: http://www.rubydoc.info/gems/sinatra/1.1.0/file/README.rdoc
|
455
|
+
[7]: http://rubydoc.info/gems/dm-core/1.0.2/frames
|
456
|
+
[8]: https://github.com/jorrizza/sinatra-mvc
|
457
|
+
[9]: https://bitbucket.org/jorrizza/sinatra-mvc
|
458
|
+
[12]: http://www.rubydoc.info/gems/dm-migrations/1.0.2/frames
|
459
|
+
[13]: http://www.rubydoc.info/gems/dm-aggregates/1.0.2/frames
|
460
|
+
[14]: http://www.rubydoc.info/gems/dm-validations/1.0.2/frames
|
461
|
+
[15]: http://www.sinatrarb.com/configuration.html
|
462
|
+
[16]: http://r18n.rubyforge.org/sinatra.html
|
463
|
+
[17]: http://gembundler.com/man/gemfile.5.html
|