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/bin/sinatra-mvc ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby1.9.1
2
+
3
+ # Determine the location of our project
4
+ PROJECT = ENV['PROJECT'] ? ENV['PROJECT'] : '.'
5
+
6
+ Dir.chdir PROJECT do
7
+ require 'sinatra-mvc'
8
+ end
@@ -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])
@@ -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,7 @@
1
+ # Like rails, make h() escape HTML characters.
2
+
3
+ helpers do
4
+ def h(s)
5
+ Rack::Utils.escape_html s
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # Flash messages on redirect
2
+ require 'rack-flash'
3
+ use Rack::Flash
4
+ require 'sinatra/redirect_with_flash'
@@ -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
@@ -0,0 +1,4 @@
1
+ *~
2
+ vendor
3
+ bin
4
+ .bundle
data/skel/.hgignore ADDED
@@ -0,0 +1,7 @@
1
+ # use glob syntax.
2
+ syntax: glob
3
+
4
+ *~
5
+ vendor
6
+ bin
7
+ .bundle
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,3 @@
1
+ get '/' do
2
+ erubis :index
3
+ end
@@ -0,0 +1,5 @@
1
+ # The 404 page.
2
+
3
+ not_found do
4
+ erubis :not_found
5
+ end
@@ -0,0 +1,10 @@
1
+ # Environment File
2
+ # Require all the libraries used in your project here.
3
+
4
+ # Datamapper Modules
5
+ require 'dm-aggregates'
6
+ require 'dm-migrations'
7
+
8
+ # Templating Engines
9
+ #require 'redcloth'
10
+ #require 'liquid'
@@ -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
@@ -0,0 +1,8 @@
1
+ # Rackup file for Sinatra-MVC
2
+
3
+ ::RACKUP = true
4
+ ::PROJECT = File.expand_path(File.dirname(__FILE__))
5
+ Dir.chdir ::PROJECT do
6
+ require 'sinatra-mvc'
7
+ run Sinatra::Application
8
+ end
data/skel/i18n/en.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ errors:
3
+ pagenotfound: !!markdown
4
+ The page at _%1_ can not be found.
5
+
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
@@ -0,0 +1,3 @@
1
+ # Uogrades the database if asked to.
2
+
3
+ DataMapper.auto_upgrade!
@@ -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