aeonscope-rest 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = v1.0.0
2
+
3
+ * Initial version.
data/LICENSE.rdoc ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Brooke Kuhlmann of {Berserk Technologies}[http://www.berserktech.com]
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.
data/README.rdoc ADDED
@@ -0,0 +1,172 @@
1
+ = Overview
2
+
3
+ Enables default REST functionality, more than what you get with Rails out-of-the-box, and keeps your code DRY. This means you can write code like this:
4
+
5
+ class Posts < ApplicationController
6
+ include Rest
7
+ end
8
+
9
+ Which would automatically yield the following REST actions:
10
+
11
+ * index
12
+ * show
13
+ * new
14
+ * edit
15
+ * create
16
+ * update
17
+ * destroy
18
+
19
+ How is that for being {DRY}[http://en.wikipedia.org/wiki/DRY]? Read on to learn more.
20
+
21
+ = License
22
+
23
+ Copyright (c) 2008-2009 Brooke Kuhlmann of {Berserk Technologies}[http://www.berserktech.com].
24
+ See the included LICENSE for more info.
25
+
26
+ = History
27
+
28
+ See the CHANGELOG file for more info.
29
+
30
+ = Requirements
31
+
32
+ 1. {Ruby on Rails}[http://rubyonrails.org] (automatically installed for you if you don't have 2.3.x or higher).
33
+ 2. mislav-will_paginate[http://github.com/mislav/will_paginate/tree/master] gem (automatically installed for you).
34
+ 3. Knowledge of the {Representational State Transfer (REST)}[http://en.wikipedia.com/wiki/REST]. Download and read {RESTful Rails}[http://www.b-simple.de/documents] if you need further info.
35
+
36
+ = Installation
37
+
38
+ Type the following from the command line to install:
39
+
40
+ * *UNIX*: sudo gem install aeonscope-rest
41
+ * *Windows*: gem install aeonscope-rest
42
+
43
+ Update your environment.rb file to include the new gem:
44
+
45
+ * config.gem "rest"
46
+
47
+ To apply unobtrusive jQuery support, run the following generator (TIP: suffix the command line with -h option for usage):
48
+
49
+ * script/generate ujs_setup
50
+
51
+ = Usage
52
+
53
+ As mentioned in the overview, simply add the following line of code to your controller(s) to make them RESTful:
54
+
55
+ include Rest
56
+
57
+ Example:
58
+
59
+ class Posts < ApplicationController
60
+ include Rest
61
+ end
62
+
63
+ This will automatically create the seven REST actions (index, show, new, create, edit, update, and destroy) for your controller. The model (i.e. Post) and model instance (i.e. @posts or @post depending on the action) are automatically determined from the controller name and created for you as well which means you can immediately write the following code in your views:
64
+
65
+ <b>index.html.erb</b>
66
+
67
+ <h2>Posts</h2>
68
+ <div>
69
+ <table>
70
+ <thead>
71
+ <tr>
72
+ <th>Label</th>
73
+ <th>Created At</th>
74
+ <th>Updated At</th>
75
+ <th>Actions</th>
76
+ </tr>
77
+ </thead>
78
+ <tbody>
79
+ <%= render @posts %>
80
+ </tbody>
81
+ </table>
82
+ </div>
83
+
84
+ <b>show.html.erb</b>
85
+
86
+ <h2>Label</h2>
87
+ <p><%= @post.label %></p>
88
+
89
+ <h2>Content</h2>
90
+ <p><%= @post.content %></p>
91
+
92
+ <p><%= link_to "Back", :back %></p>
93
+
94
+ <b>new.html.erb</b>
95
+
96
+ <% form_for @post do |form| %>
97
+ <%= form.error_messages :header_data => :strong, :header_message => "Error" %>
98
+
99
+ <div>
100
+ <%= form.label :label %><br/>
101
+ <%= form.text_field :label %>
102
+ </div>
103
+
104
+ <div>
105
+ <%= form.label :content %><br/>
106
+ <%= form.text_area :content %>
107
+ </div>
108
+
109
+ <div><%= show_submit_and_cancel :cancel_options => posts_path %></div>
110
+ <% end %>
111
+
112
+ <b>edit.html.erb</b>
113
+
114
+ Use the same code as shown in the new.html.erb view above. ;-) The Rails form_for helper will automatically determine what action to take as shown in the following code:
115
+
116
+ <% form_for @post do |form| %>
117
+
118
+ To customize the RESTful behavior of your controller, use any combination of these three macros:
119
+
120
+ * *belongs_to* - Enables resource nesting where a controller can belong to a parent controller. This behavior is similar to the ActiveRecord {belongs_to}[http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/belongs_to] macro.
121
+ * *resource_options* - Allows you to customize the default behavior of your controller(s). There is a lot you can do with this, read the code documentation for more info.
122
+ * *disabled_actions* - Allows you to disable any of the default REST actions. Follow the link to learn more.
123
+
124
+ Example:
125
+
126
+ class Comments < ApplicationController
127
+ include Rest
128
+ belongs_to :posts
129
+ resource_options :label => "My Wicked Comments"
130
+ disabled_actions :show, :destroy
131
+ end
132
+
133
+ Here is the breakdown, line-by-line, of the example shown above:
134
+
135
+ 1. Enables restful behavior.
136
+ 2. Identifies the controller as a nested resource of posts.
137
+ 3. Instead of using the default label "Comments", a customized label of "My Wicked Comments" is used instead.
138
+ 4. The "show" and "destroy" actions are disabled which means only the following actions will work: index, new, edit, create, and update.
139
+
140
+ Using the post and comment controller relationship as defined above, we can break this relationship down even further. The post (parent) resource would have the following values (in this case, all default values):
141
+
142
+ * *parent_key* = N/A
143
+ * *parent_value* = N/A
144
+ * *parent_resource_method* = N/A
145
+ * *name* = "posts"
146
+ * *label* = "Posts"
147
+ * *controller* = PostsController
148
+ * *model* = Post
149
+ * *record* = #<Post id: 1, label: "Test", content: "Test", created_at: "2008-10-31 23:59:28", updated_at: "2008-10-31 23:59:28">
150
+ * *namespaces* = []
151
+ * *show_partial* = "/posts/show"
152
+ * *new_or_edit_partial* = "/posts/new_or_edit"
153
+
154
+ The comment (child) resource would have the following values:
155
+
156
+ * *parent_key* = post_id
157
+ * *parent_value* = 1
158
+ * *parent_resource_method* = N/A
159
+ * *name* = "comments"
160
+ * *label* = "My Wicked Comments"
161
+ * *controller* = CommentsController
162
+ * *model* = Comment
163
+ * *record* = #<Post id: 1, post_id: nil, label: "Test", content: "Test", created_at: "2008-10-31 23:59:28", updated_at: "2008-10-31 23:59:28">
164
+ * *namespaces* = []
165
+ * *show_partial* = "/comments/show"
166
+ * *new_or_edit_partial* = "/comments/new_or_edit"
167
+
168
+ = Contact/Feedback/Issues
169
+
170
+ * {Berserk Technologies}[http://www.berserktech.com] - Company web site.
171
+ * Aeonscope[http://www.aeonscope.net] - Personal web site.
172
+ * Twitter[http://www.twitter.com/Aeonscope] - Short bursts of insight and/or noise.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rest"
8
+ gem.summary = "Enables default REST functionality, more than what you get with Rails out-of-the-box, and keeps your code DRY."
9
+ gem.description = "Ruby on Rails supports RESTful routing out-of-the-box. However, as you move along, you will often find yourself repeating the code for the seven REST actions: index, show, new, create, edit, update, destroy. This gem allows you to get all of the code for free by simply typing 'include Rest' at top of your controller code. That's it! Even better, you can have nested resources as well by adding a 'belongs_to' statement to your controllers much like you would for ActiveRecord models. This is just the tip of the iceburg so make sure to read the RDoc for more info."
10
+ gem.authors = ["Brooke Kuhlmann"]
11
+ gem.email = "aeonscope@gmail.com"
12
+ gem.homepage = "http://github.com/aeonscope/rest"
13
+ gem.required_ruby_version = ">= 1.8.6"
14
+ gem.add_dependency "rails", ">= 2.3.2"
15
+ gem.add_dependency "mislav-will_paginate", ">= 2.3.7"
16
+ gem.rdoc_options << "CHANGELOG.rdoc"
17
+ gem.files = FileList["[A-Z]*", "{bin,lib,generators,rails_generators,test,spec}/**/*"]
18
+ end
19
+ rescue LoadError
20
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
21
+ end
22
+
23
+ require 'spec/rake/spectask'
24
+ Spec::Rake::SpecTask.new(:spec) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.spec_files = FileList['spec/**/*_spec.rb']
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ if File.exist?('VERSION.yml')
41
+ config = YAML.load(File.read('VERSION.yml'))
42
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
43
+ else
44
+ version = ""
45
+ end
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "rest #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
52
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 0
4
+ :patch: 0
data/lib/actions.rb ADDED
@@ -0,0 +1,230 @@
1
+ module Rest
2
+ module Actions
3
+ # Default index action. Feel free to override.
4
+ def index
5
+ build_resources
6
+ if @resources.size > 1
7
+ # Records for a nested resource (act on the second-to-last parent).
8
+ parent = @resources[@resources.size - 2][:record]
9
+ records_name = parent.class.name.underscore.pluralize
10
+ records = parent.instance_eval("#{@resources.last[:parent_resource_method] || @resources.last[:name]}").paginate :page => params[:page], :per_page => 10
11
+ else
12
+ # Records for single resource.
13
+ records_name = get_model_name
14
+ if records_name
15
+ records_name = records_name.pluralize
16
+ records = @resources.last[:model].all.paginate(:page => params[:page], :per_page => 10)
17
+ end
18
+ end
19
+ instance_variable_set "@#{records_name}", records if records_name
20
+ end
21
+
22
+ # Default show action. Feel free to override.
23
+ def show
24
+ build_resources
25
+ render_action_partial @resources.last[:show_partial]
26
+ end
27
+
28
+ # Default new action. Feel free to override.
29
+ def new
30
+ build_resources
31
+ render_new_or_edit_partial
32
+ end
33
+
34
+ # Default edit action. Feel free to override.
35
+ def edit
36
+ build_resources
37
+ render_new_or_edit_partial
38
+ end
39
+
40
+ # Default create action. Feel free to override.
41
+ def create
42
+ build_resources
43
+ if get_record.update_attributes params[get_model_symbol]
44
+ redirect_to build_resource_url(@resources)
45
+ else
46
+ render_new_or_edit_partial
47
+ end
48
+ end
49
+
50
+ # Default update action. Feel free to override.
51
+ def update
52
+ build_resources
53
+ if get_record.update_attributes params[get_model_symbol]
54
+ redirect_to build_resource_url(@resources)
55
+ else
56
+ render_new_or_edit_partial
57
+ end
58
+ end
59
+
60
+ # Default destroy action. Feel free to override.
61
+ def destroy
62
+ build_resources
63
+ get_record.destroy
64
+ @resources.last.delete :record
65
+ redirect_to build_resource_url(@resources)
66
+ end
67
+
68
+ protected
69
+
70
+ # Convenience method for rendering the action partials.
71
+ def render_action_partial partial
72
+ symbol = get_model_symbol
73
+ record = get_record
74
+ if symbol && record
75
+ render :partial => partial, :layout => true, :locals => {symbol => record, :resources => @resources}
76
+ else
77
+ render :partial => partial, :layout => true, :locals => {:resources => @resources}
78
+ end
79
+ end
80
+
81
+ # Convenience method for rendering the new or edit partial.
82
+ def render_new_or_edit_partial
83
+ render_action_partial @resources.last[:new_or_edit_partial]
84
+ end
85
+
86
+ # Builds the RESTful parent URL based on an array of resources.
87
+ def build_parent_url resources = []
88
+ namespaces = resources.last[:namespaces]
89
+ url = namespaces && !namespaces.empty? ? '/' + resources.last[:namespaces].join('/') : nil
90
+ if resources.size > 1
91
+ resources.slice(0...resources.size - 1).each do |resource|
92
+ url = [url, resource[:name], resource[:parent_id]].join('/')
93
+ end
94
+ end
95
+ url
96
+ end
97
+
98
+ # A convenience method for builing RESTful URLs based on an array of resources with the
99
+ # option to override the default action. This is also defined as a helper method (see base.rb).
100
+ def build_resource_url resources = [], action = :index
101
+ name = resources.last[:name]
102
+ id = resources.last[:record].id if resources.last[:record]
103
+ parent_url = build_parent_url(resources)
104
+ url = parent_url ? [parent_url, name].join('/') : '/' + name
105
+ case action
106
+ when :show then url = [url, id].compact.join('/')
107
+ when :new then url = [url, "new"].compact.join('/')
108
+ when :edit then url = [url, id, "edit"].compact.join('/')
109
+ when :update then url = [url, id].compact.join('/') if name == name.pluralize
110
+ when :destroy then url = [url, id].compact.join('/') if name == name.pluralize
111
+ end
112
+ logger.debug "Resource URL: #{url}"
113
+ url
114
+ end
115
+
116
+ private
117
+
118
+ # Answers the name of the current model.
119
+ def get_model_name
120
+ model = @resources.last[:model]
121
+ model ? model.name.underscore : nil
122
+ end
123
+
124
+ # Answers the symbol of the current model.
125
+ def get_model_symbol
126
+ name = get_model_name
127
+ name ? name.to_sym : nil
128
+ end
129
+
130
+ # Answers the current record (a.k.a. the record of the last resource).
131
+ def get_record
132
+ name = get_model_name
133
+ name ? instance_variable_get("@#{name}") : nil
134
+ end
135
+
136
+ # Builds all resources for the controller(s).
137
+ def build_resources
138
+ @resources ||= []
139
+ if @resources.empty?
140
+ controller_name = self.class.name
141
+ add_resource controller_name, @resources
142
+ @resources.reverse!
143
+ end
144
+ # Convenience for accessing the current record.
145
+ name = get_model_name
146
+ instance_variable_set "@#{name}", @resources.last[:record] if name
147
+ end
148
+
149
+ # Convenience method for answering back a properly camelized controller name.
150
+ def camelize_controller_name name
151
+ name = name.gsub(/^(\w)/) {|c| c.capitalize}
152
+ name += "Controller" unless name.include? "Controller"
153
+ name
154
+ end
155
+
156
+ # Answers the child or parent controller namespaces (array) and name (string).
157
+ # Accepts the following argruments:
158
+ # * *full_name* - The fully namespaced controller (i.e. PostsController) or parent name (i.e. protected_pages).
159
+ # Usage:
160
+ # * Input: Public::PostsController, Output: [Public], "PostsController"
161
+ # * Input: member_manage_posts, Output: [Member, Manage], "PostsController"
162
+ # * Input: posts, Output: nil, "PostsController"
163
+ def get_controller_namespaces_and_name full_name
164
+ if full_name
165
+ delimiter = full_name.include?("Controller") ? "::" : '_'
166
+ if full_name.include? delimiter
167
+ namespaces = full_name.split delimiter
168
+ name = camelize_controller_name namespaces.pop
169
+ return namespaces.collect(&:capitalize), name
170
+ else
171
+ return nil, camelize_controller_name(full_name)
172
+ end
173
+ end
174
+ end
175
+
176
+ # Recursively walks the controller belongs_to hierarchy (if any) adding new resources along the way.
177
+ def add_resource controller_name, resources
178
+ logger.debug "Adding resource '#{controller_name}' to resources (size = #{resources.size})."
179
+ resource = configure_resource controller_name
180
+ if resource
181
+ resources << resource
182
+ # Recursively add parents (if any).
183
+ if resource[:controller].methods.include? "parent_resource"
184
+ add_resource resource[:controller].parent_resource, resources
185
+ end
186
+ end
187
+ end
188
+
189
+ # Configures a resource via belongs_to name refrence in controller. By default the controller and model
190
+ # are assumed to use the same root name regardless of singular or plural context.
191
+ def configure_resource controller_name
192
+ namespaces, name = get_controller_namespaces_and_name controller_name
193
+ controller = [namespaces, name].compact.join("::").constantize
194
+ name = name.chomp("Controller").underscore
195
+ parent_key = name.singularize + "_id"
196
+ resource = controller.resource_options || {}
197
+ resource.reverse_merge! :parent_key => parent_key,
198
+ :parent_id => params[parent_key],
199
+ :name => name,
200
+ :controller => controller,
201
+ :label => name.capitalize,
202
+ :namespaces => (namespaces.collect(&:downcase) if namespaces),
203
+ :show_partial => '/' + [namespaces, name, "show"].compact.join('/').downcase,
204
+ :new_or_edit_partial => '/' + [namespaces, name, "new_or_edit"].compact.join('/').downcase
205
+ begin
206
+ resource.reverse_merge! :model => name.singularize.camelize.constantize
207
+ rescue NameError
208
+ logger.warn "Unable to constantize: " + name
209
+ end
210
+ add_record resource
211
+ end
212
+
213
+ # Adds the current record to the resource based on position in chain.
214
+ def add_record resource
215
+ if resource[:model]
216
+ if params.include? resource[:parent_key]
217
+ # Nested parent.
218
+ resource[:record] = resource[:model].find resource[:parent_id] if resource[:parent_id]
219
+ else
220
+ # Single resource and/or end of nested chain.
221
+ resource[:record] = params[:id] ? resource[:model].find(params[:id]) : resource[:model].new
222
+ end
223
+ else
224
+ logger.error "Invalid model. Check that your controller and model are of the same name, otherwise specify the model as a resource option."
225
+ end
226
+ resource
227
+ end
228
+ end
229
+ end
230
+
@@ -0,0 +1,35 @@
1
+ module Rest
2
+ module ClassMethods
3
+ # Allows an object to belong to a parent object, thus creating a nested hierarchy. This behaves in a similar
4
+ # fashion to the belongs_to ActiveRecord macro. Accepts the following parameters:
5
+ # * *parent* - The parent symbol (including namespaces delimited by underscores). Be sure to reflect singular or plural forms on the name. Example: Public::PostsController, Result: :public_posts
6
+ def belongs_to parent
7
+ @parent_resource = parent.to_s
8
+ class_eval "class << self; attr_accessor :parent_resource end"
9
+ end
10
+
11
+ # Allows one to specify the various options for a resource. The following options are accepted:
12
+ # * *parent_key* - The ID key of the parent resource (for nested resources only). Defaults to: <belongs_to name>_id
13
+ # * *parent_value* - The ID value of the parent resource (for nested resources only). Defaults to the record id.
14
+ # * *parent_resource_method* - The instance method to call for acquiring child resource(s) of the parent (for nested resources only). Example: A post with many comments would be post.comments. Defaults to child resource name.
15
+ # * *name* - The resource name. Defaults to the controller name.
16
+ # * *label* - The resource label. Defaults to the controller name with capitalization.
17
+ # * *controller* - The controller class. Defaults to the current class. You should not need to change this.
18
+ # * *model* - The model class. Defaults to a model of the same name as the controller (minus namespace, suffix, and pluralization of course).
19
+ # * *record* - The record (new or found) related to the resource.
20
+ # * *namespaces* - The namespaces (if any) for routing. Defaults to whatever namespace your controller is using.
21
+ # * *show_partial* - The show partial. Defaults to "/<namspace(s)>/<controller name>/_show".
22
+ # * *new_or_edit_partial* - The new or edit partial. Defaults to "/<namspace(s)>/<controller name>/_new_or_edit".
23
+ def resource_options options = {}
24
+ @resource_options = options
25
+ class_eval "class << self; attr_reader :resource_options end"
26
+ end
27
+
28
+ # Allows one to disable any number of default actions. Accepts the following parameters:
29
+ # * *actions* - A variable list of REST action symbols you wish to disable. You may use any of the following: index, show, new, create, edit, update, and/or destroy.
30
+ def disabled_actions *actions
31
+ actions.uniq.each {|action| undef_method action}
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,63 @@
1
+ module ResourceHelper
2
+ # Builds a DOM ID for a given record. Works the same as the dom_id helper found in Rails except that it returns a record ID with
3
+ # a "_0" suffix for new records instead of a "new_" prefix. This makes attaching JavaScript events easier since all DOM IDs are numbers.
4
+ def build_dom_id record
5
+ name = record.class.name.underscore
6
+ record.new_record? ? name + "_0" : name + '_' + record.id.to_s
7
+ end
8
+
9
+ # Show a descriptive label based on the current controller action. Useful for new/edit actions. Accepts the following parameters:
10
+ # * *label* - The action label.
11
+ def show_action_label label
12
+ [params[:action].capitalize, label].compact.join ' '
13
+ end
14
+
15
+ # Shows an unobtrusive jQuery link where the UJS event is attached to the link via the "destroy" class.
16
+ # If JavaScript is disabled then the _show_ action will be called instead of the _destroy_ action. Accepts
17
+ # the following hash arguments:
18
+ # * *parent_id* - The ID of the parent element for which the link is a child of. Example: post_1. *NOTE:* The parent ID takes precidence over the link ID if defined.
19
+ # * *id* - The link ID. Example: post_1_link. *NOTE:* The link ID _must_ include the parent ID.
20
+ # * *label* - The link label. Defaults to "Delete".
21
+ # * *url* - The destroy URL. Example: /posts/1. *NOTE:* The proper HTTP POST request will be handled by JavaScript.
22
+ def show_destroy_link options = {}
23
+ options.reverse_merge! :id => options[:parent_id].to_s + "_destroy"
24
+ options.reverse_merge! :label => "Delete", :class => "destroy", :url => '#' + options[:id].to_s
25
+ link_to options[:label], options[:url], :id => options[:id], :class => options[:class]
26
+ end
27
+
28
+ # Shows a nested, unobtrusive jQuery link where the UJS event is attached to the link via the "destroy-nested" class.
29
+ # If JavaScript is disabled then the _show_ action will be called instead of the _destroy_ action. Accepts
30
+ # the following hash arguments:
31
+ # * *object* - The model object to destroy.
32
+ # * *parent_id* - The ID of the parent element for which the link is a child of. Example: post_1. *NOTE:* The parent ID takes precidence over the link ID if defined.
33
+ # * *id* - The link ID. Example: post_1_link. *NOTE:* The link ID _must_ include the parent ID.
34
+ # * *label* - The link label. Defaults to "Delete".
35
+ # * *url* - The destroy URL. Example: /posts/1. *NOTE:* The proper HTTP POST request will be handled by JavaScript.
36
+ def show_destroy_nested_link options = {}
37
+ unless options[:object].new_record?
38
+ options.reverse_merge! :id => options[:parent_id].to_s + "_destroy-nested", :class => "destroy-nested"
39
+ show_destroy_link options
40
+ end
41
+ end
42
+
43
+ # Shows an unobtrusive jQuery link based on an array of resource hashes. See the show_destroy_link above
44
+ # for further details. Accepts the following arguments:
45
+ # * *resources* - The array of resource hashes.
46
+ def show_destroy_resource_link resources, parent_dom_id
47
+ show_destroy_link :parent_id => parent_dom_id, :url => build_resource_url(resources, :destroy)
48
+ end
49
+
50
+ # Shows an unobtrusive jQuery link based on an array of resource hashes. See the show_destroy_link above
51
+ # for further details. Accepts the following arguments:
52
+ # * *resources* - The array of resource hashes.
53
+ def show_destroy_nested_resource_link resources, parent_dom_id
54
+ show_destroy_link(:id => parent_dom_id.to_s + "_destroy-nested", :class => "destroy-nested") unless resources.last[:record].new_record?
55
+ end
56
+
57
+ # Shows edit and delete links for resources. Accepts the following parameters:
58
+ # * *resources* - The array of resource hashes.
59
+ # * *parent_dom_id* - The parent ID of the dom object for which the edit and destroy links belong to for UJS manipulation.
60
+ def show_edit_and_destroy_resource_links resources, parent_dom_id
61
+ [link_to("Edit", build_resource_url(resources, :edit)), show_destroy_resource_link(resources, parent_dom_id)].join(" | ")
62
+ end
63
+ end
data/lib/rest.rb ADDED
@@ -0,0 +1,12 @@
1
+ require File.join(File.dirname(__FILE__), "class_methods.rb")
2
+ require File.join(File.dirname(__FILE__), "actions.rb")
3
+ require File.join(File.dirname(__FILE__), "resource_helper.rb")
4
+
5
+ module Rest
6
+ def self.included base
7
+ base.extend ClassMethods
8
+ base.helper "resource"
9
+ base.helper_method :build_resource_url
10
+ end
11
+ include Actions
12
+ end
@@ -0,0 +1,11 @@
1
+ Berserk Technologies UJS Setup Generator
2
+
3
+ Description:
4
+ Applies unobtrusive jQuery support to your Ruby on Rails application.
5
+
6
+ Requirements
7
+ 1. jQuery 1.3 or higher.
8
+ 2. jQuery UI 1.7 or higher.
9
+
10
+ Usage:
11
+ script/generate ujs_setup
@@ -0,0 +1,14 @@
1
+
2
+ Tasks You Need to Complete:
3
+ 1. Update your layouts/application.html.erb as follows:
4
+
5
+ <%= javascript_include_tag "jquery" %>
6
+ <%= javascript_include_tag "jquery-ui" %>
7
+ <%= javascript_include_tag "ujs" %>
8
+ <%= javascript_include_tag "rest" %>
9
+
10
+ 2. Add the following to the very bottom of your config/routes.rb file:
11
+
12
+ # Defaults
13
+ map.connect ":controller/:action/.:format"
14
+
@@ -0,0 +1,6 @@
1
+ # Enables dynamic javascript, namely Unobtrusive JavaScript (UJS) support for jQuery.
2
+ class JavascriptsController < ApplicationController
3
+ # Serves global jQuery UJS settings. See the /views/layouts/application.html.erb for usage.
4
+ def ujs
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ // Ensures AJAX requests are properly formated so that Rails knows how to respond to them.
2
+ $.ajaxSetup({
3
+ beforeSend: function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
4
+ });
5
+
6
+ /* Ensures AJAX requests have the proper Rails authenticity token. See the following
7
+ for more info:
8
+
9
+ http://henrik.nyh.se/2008/05/rails-authenticity-token-with-jquery
10
+ http://dev.jquery.com/ticket/3387
11
+ */
12
+ $(document).ajaxSend(function(event, request, settings){
13
+ var authToken = "<%= form_authenticity_token %>";
14
+ if (authToken != null){
15
+ settings.data = settings.data || "";
16
+ settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(authToken);
17
+ };
18
+ });
@@ -0,0 +1,119 @@
1
+ // Chops off the last string segment designated by delimiter. If no delimiter is found then the original string is
2
+ // returned instead. The following attributes are accepted:
3
+ // string = Required. The string to chop.
4
+ // delimiter = Optional. The delimiter used to chop up the string. Defaults to '_'.
5
+ function stringChop(string, delimiter) {
6
+ var chopped = string;
7
+ if (delimiter == undefined) {delimiter = '_';}
8
+ var endIndex = string.lastIndexOf(delimiter);
9
+ if (endIndex > 1) {chopped = string.slice(0, endIndex);}
10
+ return chopped;
11
+ };
12
+
13
+ // Increments a number embedded in the text by one. If no number is found then the original text is returned.
14
+ function incrementText(text) {
15
+ if (text != undefined) {
16
+ var match = text.match(/\d+/);
17
+ if (match != null) {
18
+ var number = new Number(match);
19
+ var newNumber = number + 1;
20
+ text = text.replace(number, newNumber);
21
+ }
22
+ }
23
+ return text;
24
+ };
25
+
26
+ // Increments the input field ID number by one so ActiveRecord can save new record attributes.
27
+ function incrementInputId(input) {
28
+ id = $(input).attr("id");
29
+ id = incrementText(id);
30
+ $(input).attr("id", id);
31
+ };
32
+
33
+ // Increments the input field name number by one so ActiveRecord can save new record attributes.
34
+ function incrementInputName(input) {
35
+ name = $(input).attr("name");
36
+ name = incrementText(name);
37
+ $(input).attr("name", name);
38
+ };
39
+
40
+ // Generates a hidden input field based off original input field data that instructs ActiveRecord to delete
41
+ // a record based on the ID of the input field.
42
+ function generateDestroyInput(input) {
43
+ $(input).attr("id", stringChop($(input).attr("id")) + "__delete");
44
+ $(input).attr("name", stringChop($(input).attr("name"), '[') + "[_delete]");
45
+ $(input).attr("type", "hidden");
46
+ $(input).attr("value", 1);
47
+ return input;
48
+ };
49
+
50
+ // Animates the removal of a DOM element.
51
+ function animateDestroy(id) {
52
+ $(id).animate({backgroundColor: "#FF0000", border: "0.2em solid #FF0000"}, 500);
53
+ $(id).fadeOut(500);
54
+ };
55
+
56
+ // UJS
57
+ $(document).ready(function(){
58
+ // Nested New
59
+ $("a.new-nested").click(function(){
60
+ var parentId = '#' + stringChop($(this).attr("id"));
61
+ var child = $(parentId).children(":last").clone(true);
62
+ var childId = child.attr("id");
63
+ child.attr("id", incrementText(childId));
64
+ $(parentId).append(child);
65
+ // Ensure the cloned input fields are unique.
66
+ $('#' + child.attr("id") + " :input").each(function(){
67
+ incrementInputId(this);
68
+ incrementInputName(this);
69
+ $(this).attr("value", '');
70
+ return this;
71
+ });
72
+ // Ensure the cloned destroy link is unique.
73
+ destroyLink = $("a.destroy-nested:last");
74
+ destroyLink.attr("id", incrementText(destroyLink.attr("id")));
75
+ // Ensure that the default event does not fire.
76
+ return false;
77
+ });
78
+
79
+ // Nested Destroy
80
+ $("a.destroy-nested").click(function(){
81
+ try {
82
+ var id = $(this).attr("id");
83
+ var parentId = stringChop(id);
84
+ if (parentId != id) {
85
+ parentId = '#' + parentId;
86
+ $(parentId).prepend(generateDestroyInput($(parentId + " input:first").clone()));
87
+ animateDestroy(parentId);
88
+ } else {
89
+ throw "Invalid ID";
90
+ }
91
+ } catch (e) {
92
+ alert("Error: " + e + ". Check that parent ID is defined and/or the link ID includes parent ID as part of the link ID.");
93
+ }
94
+ // Ensure that the default event does not fire.
95
+ return false;
96
+ });
97
+
98
+ // Destroy
99
+ $("a.destroy").click(function(){
100
+ var result = confirm("Are you sure you want to delete this?");
101
+ if (result) {
102
+ try {
103
+ var id = $(this).attr("id");
104
+ var parentId = stringChop(id);
105
+ if (parentId != id) {
106
+ animateDestroy('#' + parentId);
107
+ } else {
108
+ throw "Invalid ID";
109
+ }
110
+ // Finally, call the destroy action.
111
+ $.post($(this).attr("href"), "_method=delete");
112
+ } catch (e) {
113
+ alert("Error: " + e + ". Check that parent ID is defined and/or the link ID includes parent ID as part of the link ID.");
114
+ }
115
+ }
116
+ // Ensure that the default event does not fire.
117
+ return false;
118
+ });
119
+ });
@@ -0,0 +1,18 @@
1
+ class UjsSetupGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ # Controllers
5
+ m.file "app/controllers/javascripts_controller.rb", "app/controllers/javascripts_controller.rb"
6
+
7
+ # Views
8
+ m.directory "app/views/javascripts"
9
+ m.file "app/views/javascripts/ujs.js.erb", "app/views/javascripts/ujs.js.erb"
10
+
11
+ # JavaScript
12
+ m.file "public/javascripts/rest.js", "public/javascripts/rest.js"
13
+
14
+ # Instructions
15
+ m.readme "README"
16
+ end
17
+ end
18
+ end
data/spec/rest_spec.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Rest" do
4
+ it "should be testable" do
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'rest'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aeonscope-rest
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brooke Kuhlmann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-03 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.3.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mislav-will_paginate
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.3.7
34
+ version:
35
+ description: "Ruby on Rails supports RESTful routing out-of-the-box. However, as you move along, you will often find yourself repeating the code for the seven REST actions: index, show, new, create, edit, update, destroy. This gem allows you to get all of the code for free by simply typing 'include Rest' at top of your controller code. That's it! Even better, you can have nested resources as well by adding a 'belongs_to' statement to your controllers much like you would for ActiveRecord models. This is just the tip of the iceburg so make sure to read the RDoc for more info."
36
+ email: aeonscope@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE.rdoc
43
+ - README.rdoc
44
+ files:
45
+ - CHANGELOG.rdoc
46
+ - LICENSE.rdoc
47
+ - README.rdoc
48
+ - Rakefile
49
+ - VERSION.yml
50
+ - lib/actions.rb
51
+ - lib/class_methods.rb
52
+ - lib/resource_helper.rb
53
+ - lib/rest.rb
54
+ - rails_generators/ujs_setup/USAGE
55
+ - rails_generators/ujs_setup/templates/README
56
+ - rails_generators/ujs_setup/templates/app/controllers/javascripts_controller.rb
57
+ - rails_generators/ujs_setup/templates/app/views/javascripts/ujs.js.erb
58
+ - rails_generators/ujs_setup/templates/public/javascripts/rest.js
59
+ - rails_generators/ujs_setup/ujs_setup_generator.rb
60
+ - spec/rest_spec.rb
61
+ - spec/spec_helper.rb
62
+ has_rdoc: true
63
+ homepage: http://github.com/aeonscope/rest
64
+ post_install_message:
65
+ rdoc_options:
66
+ - --charset=UTF-8
67
+ - CHANGELOG.rdoc
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.8.6
75
+ version:
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.2.0
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Enables default REST functionality, more than what you get with Rails out-of-the-box, and keeps your code DRY.
89
+ test_files:
90
+ - spec/rest_spec.rb
91
+ - spec/spec_helper.rb