airbed 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.
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2007-08-11
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Lachie Cox
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/airbed.rb
7
+ lib/airbed/version.rb
8
+ scripts/txt2html
9
+ setup.rb
10
+ test/test_airbed.rb
11
+ test/test_helper.rb
12
+ website/index.html
13
+ website/index.txt
14
+ website/javascripts/rounded_corners_lite.inc.js
15
+ website/stylesheets/screen.css
16
+ website/template.rhtml
17
+ examples/faces.rb
@@ -0,0 +1,5 @@
1
+ Airbed makes camping restful.
2
+
3
+ Airbed works in much the same way that rails maps HTTP verbs (GET,POST,PUT and DELETE) onto CRUDdy actions (list, show, create, update, delete, (plus those two also-rans new and edit)).
4
+
5
+ However it goes a little deeper to provide default implementations and hooks for the CRUD actions.
@@ -0,0 +1,125 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ require 'hoe'
11
+
12
+ include FileUtils
13
+ require File.join(File.dirname(__FILE__), 'lib', 'airbed', 'version')
14
+
15
+ AUTHOR = 'Lachie Cox' # can also be an array of Authors
16
+ EMAIL = "lachie@smartbomb.com.au"
17
+ DESCRIPTION = "airbed makes camping restful"
18
+ GEM_NAME = 'airbed' # what ppl will type to install your gem
19
+
20
+ @config_file = "~/.rubyforge/user-config.yml"
21
+ @config = nil
22
+ def rubyforge_username
23
+ unless @config
24
+ begin
25
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
26
+ rescue
27
+ puts <<-EOS
28
+ ERROR: No rubyforge config file found: #{@config_file}"
29
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
30
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
31
+ EOS
32
+ exit
33
+ end
34
+ end
35
+ @rubyforge_username ||= @config["username"]
36
+ end
37
+
38
+ RUBYFORGE_PROJECT = 'rails-oceania' # The unix name for your project
39
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
40
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
41
+
42
+ NAME = "airbed"
43
+ REV = nil
44
+ # UNCOMMENT IF REQUIRED:
45
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
46
+ VERS = Airbed::VERSION::STRING + (REV ? ".#{REV}" : "")
47
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
48
+ RDOC_OPTS = ['--quiet', '--title', 'airbed documentation',
49
+ "--opname", "index.html",
50
+ "--line-numbers",
51
+ "--main", "README",
52
+ "--inline-source"]
53
+
54
+ class Hoe
55
+ def extra_deps
56
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
57
+ end
58
+ end
59
+
60
+ # Generate all the Rake tasks
61
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
62
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
63
+ p.author = AUTHOR
64
+ p.description = DESCRIPTION
65
+ p.email = EMAIL
66
+ p.summary = DESCRIPTION
67
+ p.url = HOMEPATH
68
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
69
+ p.test_globs = ["test/**/test_*.rb"]
70
+ p.clean_globs |= CLEAN #An array of file patterns to delete on clean.
71
+
72
+ # == Optional
73
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
74
+ p.extra_deps = [ ['camping', '>= 1.5'] ]
75
+
76
+ #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
77
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
78
+ end
79
+
80
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
81
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
82
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
83
+
84
+ desc 'Generate website files'
85
+ task :website_generate do
86
+ Dir['website/**/*.txt'].each do |txt|
87
+ sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
88
+ end
89
+ end
90
+
91
+ desc 'Upload website files to rubyforge'
92
+ task :website_upload do
93
+ host = "#{rubyforge_username}@rubyforge.org"
94
+ remote_dir = "/var/www/gforge-projects/#{PATH}/"
95
+ local_dir = 'website'
96
+ sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
97
+ end
98
+
99
+ desc 'Generate and upload website files'
100
+ task :website => [:website_generate, :website_upload, :publish_docs]
101
+
102
+ desc 'Release the website and new gem version'
103
+ task :deploy => [:check_version, :website, :release] do
104
+ puts "Remember to create SVN tag:"
105
+ puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
106
+ "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
107
+ puts "Suggested comment:"
108
+ puts "Tagging release #{CHANGES}"
109
+ end
110
+
111
+ desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
112
+ task :local_deploy => [:website_generate, :install_gem]
113
+
114
+ task :check_version do
115
+ unless ENV['VERSION']
116
+ puts 'Must pass a VERSION=x.y.z release version'
117
+ exit
118
+ end
119
+ unless ENV['VERSION'] == VERS
120
+ puts "Please update your version.rb to match the release version, currently #{VERS}"
121
+ exit
122
+ end
123
+ end
124
+
125
+
@@ -0,0 +1,91 @@
1
+ require 'airbed'
2
+ # require File.dirname(__FILE__)+'/../lib/airbed'
3
+
4
+ Camping.goes :Faces
5
+
6
+ module Faces
7
+ module Models
8
+ class Person < Base; end
9
+ end
10
+
11
+ module Controllers
12
+ include Airbed::Resources
13
+
14
+ class Index < R '/'
15
+ def get
16
+ redirect People
17
+ end
18
+ end
19
+
20
+ class People < Resource Models::Person
21
+ def after_modification(instance)
22
+ redirect(People)
23
+ end
24
+ end
25
+ end
26
+
27
+ module Views
28
+ # TODO make this work using
29
+ # include Airbed::Views
30
+ def form(options={})
31
+ verb = method = options[:method] || :post
32
+ options[:method] = :post unless [:get,:post].include? method.to_sym
33
+
34
+ tag!(:form, options) {
35
+ input(:type => 'hidden', :name => '_verb', :value => verb)
36
+ yield
37
+ }
38
+ end
39
+
40
+ def _button(text,href,method=:post)
41
+ form(:action => href, :method => method) {
42
+ input :type => 'submit', :value => text
43
+ }
44
+ end
45
+
46
+ def list_people
47
+ h1 'ppl!'
48
+ ul {
49
+ @people.each {|person|
50
+ li { a person.name, :href => R(People,person) }
51
+ _button('x',R(People,person),:delete)
52
+ }
53
+ }
54
+
55
+ form( :action => R(People), :method => 'post') {
56
+ input :name => 'person[name]'
57
+ input :type => 'submit', :value => '+'
58
+ }
59
+ end
60
+
61
+ def show_person
62
+ h1 @person.name
63
+
64
+ form(:action => R(People,@person), :method => :put) {
65
+ input :name => 'person[name]', :value => @person.name
66
+ input :type => 'submit', :value => 'save'
67
+ }
68
+ a(:href => R(People)) {"&larr; people"}
69
+ end
70
+ end
71
+
72
+
73
+ # schema
74
+ module Models
75
+ class CreateTheBasics < V 1.0
76
+ def self.up
77
+ create_table :faces_people do |t|
78
+ t.column :name, :string
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ def Faces.create
87
+ Faces::Models.create_schema
88
+ end
89
+
90
+
91
+
@@ -0,0 +1,279 @@
1
+ require 'camping'
2
+ require 'airbed/version'
3
+
4
+ module Airbed
5
+ # The Airbed::Resources module is designed to be mixed into your Controllers module.
6
+ #
7
+ # == Getting Started
8
+ #
9
+ # To let your controllers be restful
10
+ #
11
+ # 1. <tt>require 'rubygems'</tt>
12
+ # 2. <tt>require 'airbed'</tt>
13
+ # 3. Mixin airbed: <tt>module YourApp::Controllers; include Airbed::Resources; end</tt>
14
+ #
15
+ # Now, instead of deriving your controller from <tt>R</tt>, derive it from <tt>Resource</tt>:
16
+ #
17
+ # module YourApp::Controllers
18
+ # class YourModels < Resource YourModel
19
+ # end
20
+ # end
21
+ #
22
+ # You can override the various default implementations of the restful actions below by just overriding the methods.
23
+ #
24
+ module Resources
25
+
26
+ def self.included(base) # +nodoc+
27
+ base.send :extend, ClassMethods
28
+ end
29
+
30
+ # Acts as a dispatcher to map between HTTP verbs and actions
31
+ class Resty
32
+
33
+ # Maps from GET requests to restful actions
34
+ #
35
+ # * If +id+ and +action+ are provided, call the method +action+ (if implemented).
36
+ # * If only id is provided, load the +show_context+ and call show.
37
+ # * Otherwise, load the new context and call list
38
+ def get(id=nil,action=nil)
39
+
40
+ # we have id and action ... call arbitrary action
41
+ if !id.blank? and !action.blank?
42
+ model = instance_context(id)
43
+
44
+ # special case
45
+ action = (action == 'new' ? 'new_action' : action)
46
+ send(action,model)
47
+
48
+ elsif !id.blank?
49
+ model = show_context(id)
50
+ show(model)
51
+
52
+ else
53
+ list = list_context
54
+ list(list)
55
+ end
56
+ end
57
+
58
+ # Maps from POST requests to restful actions. Also supports "exotic" methods PUT and DESTROY (via the _verb parameter).
59
+ #
60
+ # 1. POST with an id is undefined
61
+ # 2. POST with no id loads +new_context+ and calls +create+.
62
+ # 3. _verb == 'put' + id delegates to +put+ the handler
63
+ # 4. _verb == 'destroy' + id deletgates to the +destroy+ handler
64
+ #
65
+ def post(id=nil)
66
+ unless id.blank?
67
+ @model = instance_context(id)
68
+
69
+ case input['_verb']
70
+ when 'put'
71
+ put(id)
72
+ when 'delete'
73
+ delete(id)
74
+ else
75
+ raise "eh? unknown verb #{input['_verb']}"
76
+ end
77
+ else
78
+ create(new_context)
79
+ end
80
+ end
81
+
82
+ # Map from PUT requests to restful actions.
83
+ #
84
+ # put + id loads +instance_context+ and calls +update+
85
+ def put(id)
86
+ update(@model || instance_context(id))
87
+ end
88
+
89
+ # Map from DELETE requests to restful actions.
90
+ #
91
+ # delete + id loads +instance_context+ and calls +destroy+
92
+ def delete(id)
93
+ destroy(@model || instance_context(id))
94
+ end
95
+
96
+
97
+ protected
98
+ # default implementation of show
99
+ #
100
+ # Renders the <tt>show_#{singular_name}</tt> view.
101
+ def show(instance)
102
+ render "show_#{singular_name}"
103
+ end
104
+
105
+ # default implementation of list
106
+ #
107
+ # Renders the <tt>list_#{plural_name}</tt> view.
108
+ def list(list)
109
+ render "list_#{plural_name}"
110
+ end
111
+
112
+ # default implementation of create
113
+ #
114
+ # It extracts the model attributes from +input+ and calls +save!+
115
+ #
116
+ # Calls +after_modification+ if defined, other wise redirects to +show+.
117
+ def create(instance)
118
+ instance.attributes = input[singular_name]
119
+ instance.save!
120
+
121
+ if respond_to? :after_modification
122
+ after_modification(instance)
123
+ else
124
+ redirect(self.class,instance)
125
+ end
126
+ end
127
+
128
+ # default implementation of update
129
+ #
130
+ # delegates to create.
131
+ def update(instance)
132
+ create(instance)
133
+ end
134
+
135
+ # default implementation of destroy
136
+ #
137
+ # Destroys the instance and redirects to +list+.
138
+ def destroy(instance)
139
+ instance.destroy
140
+ redirect(self.class)
141
+ end
142
+
143
+ ## pseudo-rest methods
144
+ # default implementation of edit
145
+ #
146
+ # Renders the <tt>form_#{singular_name}</tt> view.
147
+ def edit(instance)
148
+ render "form_#{singular_name}"
149
+ end
150
+
151
+ # default implementation of new
152
+ #
153
+ # Renders the <tt>form_#{singular_name}</tt> view.
154
+ def new_action(instance)
155
+ render "form_#{singular_name}"
156
+ end
157
+
158
+ ## utilities
159
+ # calls a method if its implemented
160
+ def hook(method,*args)
161
+ send(method,*args) if respond_to? method
162
+ end
163
+
164
+ # returns the singular, sluggy class name
165
+ def singular_name
166
+ @singular_name ||= model_class.to_s.demodulize.underscore
167
+ end
168
+
169
+ # returns the plural, sluggy class name
170
+ def plural_name
171
+ @plural_name ||= singular_name.pluralize
172
+ end
173
+
174
+ # instance version of Resty#model_class (meta defined)
175
+ def model_class; self.class.model_class ; end
176
+
177
+ # instance version of Resty#options (meta defined)
178
+ def options
179
+ @options ||= self.class.options || {}
180
+ end
181
+
182
+ # loads the context for show
183
+ #
184
+ # delegates to +instance_context+ with <tt>options[:show_options]</tt> if defined.
185
+ def show_context(id)
186
+ instance_context(id, options[:show_options] || {})
187
+ end
188
+
189
+ # loads the context for REST methods requiring an instance of the controlled model
190
+ #
191
+ # merges <tt>options[:show_options]</tt> with +instance_options+ (if supplied),
192
+ # and passes it as options to the model classes' +find+ method.
193
+ #
194
+ # Sets to <tt>@#{singular_name}</tt> to the loaded instance and returns it.
195
+ def instance_context(id,instance_options={})
196
+ instance_options = (options[:instance_options] || {}).merge(instance_options)
197
+
198
+ puts "instance context, #{instance_options.inspect}"
199
+
200
+ returning(model_class.find(id,instance_options)) do |instance|
201
+ instance_variable_set("@#{singular_name}",instance)
202
+ end
203
+ end
204
+
205
+ # loads the context for REST methods requiring a list of instances of the controlled model.
206
+ #
207
+ # Passes <tt>options[:list_options]</tt> (if defined) to the model classes' +find+ method.
208
+ #
209
+ # Sets <tt>@#{plural_name}</tt> to the list and returns it.
210
+ def list_context
211
+ list_options = options[:list_options] || {}
212
+
213
+ returning(model_class.find(:all, list_options)) do |list|
214
+ instance_variable_set("@#{plural_name}",list)
215
+ end
216
+ end
217
+
218
+ # load the context for REST methods requiring a fresh, unsaved, defaulted instance of the controlled model.
219
+ #
220
+ # Hooks +setup_defaults+ and sets <tt>@#{singular_name}</tt> to the instance and returns it.
221
+ def new_context
222
+ returning(self.class.model_class.new) do |instance|
223
+ hook(:setup_defaults,instance)
224
+ instance_variable_set("@#{singular_name}",instance)
225
+ end
226
+ end
227
+ end
228
+
229
+ module ClassMethods
230
+
231
+ # Make a controller into a resource.
232
+ #
233
+ # Just do
234
+ #
235
+ # module Camping::Controllers
236
+ # class Users < Resource User; end
237
+ # end
238
+ #
239
+ # or maybe something more complex
240
+ #
241
+ # module Camping::Controllers
242
+ # class Users < Resource User, :show_options => {:include => [:achievements]}
243
+ # def show(user)
244
+ # user.tweak!
245
+ # super
246
+ # end
247
+ # end
248
+ # end
249
+ #
250
+ def Resource(model_class,options={})
251
+
252
+ class_name = model_class.to_s.demodulize
253
+ name = options[:name] || class_name.underscore.pluralize
254
+
255
+ r = @r
256
+ u = options[:urls] || ["/#{name}", "/#{name}/(\\d+)", "/#{name}/(\\d+)/(.+)"]
257
+
258
+ Class.new(Airbed::Resources::Resty) {
259
+ meta_def(:model_class) { model_class }
260
+ meta_def(:options) { options }
261
+ meta_def(:urls) { u }
262
+ meta_def(:inherited){|x| r << x}
263
+ }
264
+ end
265
+ end
266
+ end
267
+
268
+ module Views
269
+ def form(options={})
270
+ verb = method = options[:method] || :post
271
+ options[:method] = :post unless [:get,:post].include? method.to_sym
272
+
273
+ tag!(:form, options) {
274
+ input(:type => 'hidden', :name => '_verb', :value => verb)
275
+ yield
276
+ }
277
+ end
278
+ end
279
+ end