airbed 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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