modify_resource 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in modify_resource.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Colin Young
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # ModifyResource
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ gem 'modify_resource'
8
+
9
+ And then execute:
10
+
11
+ $ bundle
12
+
13
+ Or install it yourself as:
14
+
15
+ $ gem install modify_resource
16
+
17
+ ## Usage
18
+
19
+ #### Including
20
+
21
+ Just include `ModifyResource` in your controller, or, if you want it available in your entire application:
22
+
23
+ ```ruby
24
+ # application.rb
25
+ class ApplicationController < ActionController::Base
26
+
27
+ class << self
28
+ include Rails.application.routes.url_helpers
29
+ end
30
+
31
+ end
32
+ ```
33
+
34
+ #### `redirect_to`
35
+
36
+ You can change how your successful `create`s or `update`s are handled by changing where they redirect to in the `options` hash.
37
+
38
+ Note: If you want to use the `:redirect_to` option, and you want to use Rails routes, you'll need to `include ActionController::Base` in your controller (or ApplicationController) that you also `include ModifyResource` in.
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/test*.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ desc "Run tests"
11
+ task :default => :test
@@ -0,0 +1,236 @@
1
+ require_relative 'resource_info'
2
+ require_relative 'router'
3
+
4
+ module ActionController
5
+
6
+ module ModifyResource
7
+ include ResourceInfo
8
+ include ActionView::Helpers::TextHelper
9
+
10
+ module ClassMethods
11
+
12
+ # A shortcut - just add modify_on :create, :update
13
+ # if your variable is the controller's name -- e.g. @widget in WidgetsController
14
+ def modify_on(*actions)
15
+ # Define in the method so that the user could override if they wanted
16
+ options = actions.extract_options!
17
+ actions.each do |action|
18
+ unless method_defined?(action)
19
+ define_method action do
20
+ modify_resource_with action, options
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ # :nodoc:
28
+ def self.included(base)
29
+ base.extend(ClassMethods)
30
+ end
31
+
32
+ # Handles the usual @var = Model.find(params[:id]); @var.save
33
+ # etc. stuff.
34
+ def modify_resource_with(*args) # ([res=nil, ], verb=:update, options={}, args=nil)
35
+ # Set var to resource matching controller's name if res is a symbol
36
+ if args.first.is_a? Symbol or args.first.is_a? String
37
+ res = instance_variable_get(:"@#{resource_name}")
38
+ else
39
+ res = args.shift
40
+ end
41
+
42
+ # Set up default values
43
+ verb = args[0] || :update
44
+ options = args[1] || {}
45
+ _params = args[2] || nil
46
+ as_user = nil
47
+
48
+ # model_name is this res's model_name
49
+ model_name = res.class.model_name.underscore.downcase
50
+
51
+ # Grab params if not set
52
+ _params ||= params[model_name]
53
+
54
+ # We may need the current user instance
55
+ options[:as_current] ||= options[:as_user] # Backwards compat
56
+
57
+ if options[:as_current] and res.respond_to?(:as_user)
58
+ res.as_user = self.send "current_#{options[:as_current]}"
59
+ end
60
+
61
+ # Actually perform update, or create, destroy, etc.
62
+ modified = case verb
63
+ when :update
64
+ res.update_permitted_attributes(_params)
65
+ when :create
66
+ res.deep_attributes = _params
67
+ res.persisted?
68
+ else
69
+ res.method(verb).arity == 0 ? res.send(verb) : res.send(verb, _params)
70
+ end
71
+
72
+ # the resource was updated
73
+ instance_variable_set :"@#{resource_name}", res
74
+
75
+ raise 'As_user MUST be unset on ALL items.' if res.valid? and res.as_user.present?
76
+
77
+ # We're updating a nested resource, so we need to set its parent for it
78
+ update_resource_parent(res) unless parent_for(res).present?
79
+
80
+ # Actually save or create.
81
+ if modified
82
+
83
+ unless request.xhr?
84
+
85
+ options[:redirect_with_resources] ||= [ :self ]
86
+ after = params[:after]
87
+ unless (path = options[:redirect_to]).blank?
88
+ router = Router.new
89
+ success_path = if options[:redirect_with_resources].blank?
90
+ router.send path
91
+ else
92
+ router.send path, *collect_resources_for(res, options[:redirect_with_resources])
93
+ end
94
+ end
95
+ success_path ||= resource_path_with_base(:production, res)
96
+
97
+ msg = options[:success].try(:call, res)
98
+
99
+ if msg.blank?
100
+
101
+ msg = "#{action_name.capitalize.gsub(/e$/,'')}ed "
102
+
103
+ if after.present? and after.pluralize == after
104
+ # Redirect to the plural (index) page
105
+ model_name = params[:after]
106
+ msg << model_name.pluralize
107
+ success_path << '/' + after
108
+ else
109
+ # Redirect to the show page for the resource.
110
+ # Flash is like 'Widget 2 has been updated'.
111
+ name = String.new.tap do |s|
112
+ break s = res.title if res.respond_to? :title
113
+ break s = res.name if res.respond_to? :name
114
+ s = res.id
115
+ end
116
+ msg << "#{model_name.humanize.downcase} '#{truncate(name, length: 20)}'"
117
+ end
118
+ end
119
+
120
+ flash[:notice] = msg
121
+ redirect_to success_path
122
+ else
123
+ render json: res
124
+ end
125
+ else
126
+ messages = res.errors.messages
127
+ unless request.xhr?
128
+ flash[:error] = messages
129
+ begin
130
+ render :edit
131
+ rescue
132
+ # They didn't respond to that action
133
+ if res.persisted?
134
+ render :show
135
+ else
136
+ render :new
137
+ end
138
+ end
139
+ else
140
+ render json: {errors: messages}, status: :unprocessable_entity
141
+ end
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ # :nodoc:
148
+ def resource_path_with_base(base, res)
149
+ components, resources = path_components_for(res, base).try(:compact)
150
+
151
+ return url_for unless components.present?
152
+
153
+ unless components.nil? || components.empty?
154
+ path = components.join('_') + '_path'
155
+ send(path, *resources)
156
+ else
157
+ :"#{base.to_s.pluralize}"
158
+ end
159
+ end
160
+
161
+ # :nodoc: Climbs up the resource's parent associations to generate a rails route
162
+ def path_components_for(res, base, components=[], resources=[])
163
+ return nil unless res.present?
164
+
165
+ component = component_for(res)
166
+
167
+ # Redirect to the index if the res was just deleted
168
+ component = component.pluralize unless res.persisted?
169
+
170
+ components.unshift component
171
+ resources.unshift res if res.persisted?
172
+
173
+ unless component.to_s == base.to_s
174
+ path_components_for(parent_for(res), base, components, resources)
175
+ else
176
+ [components, resources]
177
+ end
178
+ end
179
+
180
+ # :nodoc: gets path component for a resource
181
+ def component_for(res)
182
+ res.class.model_name.downcase
183
+ end
184
+
185
+ # :nodoc: gets a nested resource's parent
186
+ def parent_for(res)
187
+ parents = possible_parents_for(res)
188
+ return nil unless parents.count == 1
189
+
190
+ component = component_for(res)
191
+ parent_component = parents[0].sub('_id', '')
192
+ res.send(parent_component)
193
+ end
194
+
195
+ # :nodoc: gets possible parents based on attributes ending in '_id'
196
+ def possible_parents_for(res)
197
+ Array.new.tap do |possibilities|
198
+ res.attributes.keys.each do |field|
199
+ next unless field[/_id$/]
200
+ cleaned = field.sub '_id', ''
201
+ # Fields that end in _id but do not point to a constant are ignored.
202
+ possibilities << cleaned if Object.const_defined? cleaned.classify
203
+ end
204
+ end
205
+ end
206
+
207
+ # :nodoc: Adds the resource to its parent
208
+ def update_resource_parent(res)
209
+ parents = possible_parents_for(res)
210
+ return if parents.empty? or parents.count > 1
211
+
212
+ if parents.count > 1
213
+ raise "Can't guess parent, multiple options for resource.\n#{res.inspect}\n#{parents.inspect}"
214
+ end
215
+
216
+ parent_class = parents.first.classify.constantize
217
+ parent_name = parent_class.model_name.downcase
218
+ parent_param = parent_name + '_id'
219
+ parent = parent_class.find(params[parent_param])
220
+ return unless parent.present?
221
+
222
+ instance_variable_set :"@#{parent_name}", parent
223
+
224
+ child_name = res.class.model_name.downcase.pluralize
225
+ child_name = child_name.singularize if !parent.respond_to?(child_name)
226
+
227
+ association = parent.send(child_name)
228
+ association << res
229
+ end
230
+
231
+ # Converts a list of symbols into the items necessary to compose redirection URLS
232
+ def collect_resources_for res, arr
233
+ arr.collect { |i| res.send(i) }
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,22 @@
1
+ module ActionController
2
+ module ModifyResource
3
+ module ResourceInfo
4
+
5
+ def resource_name
6
+ controller_name[/(\w+)$/].singularize
7
+ end
8
+
9
+ def model_class
10
+ resource_name.capitalize.constantize
11
+ end
12
+
13
+ def resource
14
+ res = instance_variable_get "@#{resource_name}"
15
+ begin
16
+ res ||= model_class.find params[:id]
17
+ rescue; end
18
+ res
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ module ActionController
2
+ module ModifyResource
3
+ class Router
4
+
5
+ def initialize
6
+ self.class.send :include, Rails.application.routes.url_helpers
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,87 @@
1
+ module ActionController
2
+
3
+ module UpdateAs
4
+
5
+ module ClassMethods
6
+
7
+ def update_as_current(user_class=:user, nested_resources={})
8
+ append_before_filter do
9
+ return unless [:PUT, :POST, :PATCH].include?(request.method.to_sym) and
10
+ params[:_method] != "delete"
11
+
12
+ update_as(self.send(:"current_#{user_class}"), resource, nested_resources)
13
+ end
14
+ end
15
+
16
+ def update_as_current_user(nested_resources={})
17
+ update_as_current :user, nested_resources
18
+ end
19
+
20
+ end
21
+
22
+ def update_as(user, current_resource, nested_resources={})
23
+ case nested_resources
24
+ when Symbol, String, Array
25
+ update_resource(current_resource, Array(nested_resources), as: user)
26
+ else
27
+ return update_resource(current_resource, as: user) if nested_resources.empty?
28
+
29
+ nested_resources.each do |res, fields|
30
+ res = if res.to_s == current_resource.class.name.downcase
31
+ current_resource
32
+ else
33
+ current_resource.send(res)
34
+ end
35
+ update_resource res, fields, as: user
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.included(base)
41
+ base.extend(ClassMethods)
42
+ end
43
+
44
+ private
45
+
46
+ def add_user_id_to(hash_or_user, user_hash)
47
+ return unless hash_or_user.present?
48
+ raise UserNotProvidedError unless user = user_hash[:as]
49
+ user_class = user.class.model_name.downcase.underscore # 'user', etc.
50
+
51
+ case hash_or_user
52
+ when Hash
53
+ hash_or_user[:"#{user_class}_identifier"] = user.id
54
+ else
55
+ hash_or_user.send :"#{user_class}=", user
56
+ end
57
+ end
58
+
59
+ def update_resource(*args) # resource, (optional: fields=[]), user_hash={}
60
+ resource = args.first
61
+ user_hash = args.last
62
+ raise UserNotProvidedError unless user = user_hash[:as]
63
+ fields = args[1] if args.count > 2
64
+
65
+ resource_name = resource.class.name.downcase
66
+ parameters = params[resource_name]
67
+
68
+ if parameters.present? and fields.present?
69
+ fields.each do |field|
70
+
71
+ field_attribute = field.to_s + '_attributes' unless field[/_attributes/].present?
72
+ handle = parameters[field_attribute]
73
+
74
+ handle = [handle] unless handle.is_a?(Array)
75
+ handle.each do |individual_resource|
76
+ add_user_id_to(individual_resource, as: user)
77
+ end
78
+ end
79
+ else
80
+ add_user_id_to(resource, as: user)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ class UserNotProvidedError < StandardError
87
+ end
@@ -0,0 +1,90 @@
1
+ module ActiveModel
2
+
3
+ # This variable is to aid models methods that need the current user.
4
+ # It is very different from solutions that use Thread --
5
+ # 1. First of all, it's paired with modify_resource_with, and
6
+ # 2. I've hooked into after_save to double-ensure that the
7
+ # @as_user variable is unset after any change.
8
+
9
+ module UpdatePermittedAttributes
10
+ def update_permitted_attributes(attributes)
11
+ if attributes.all_permitted?
12
+ update_attributes(attributes, without_protection: true)
13
+ else
14
+ update_attributes(attributes)
15
+ end
16
+ end
17
+
18
+ def deep_attributes=(attributes)
19
+ attributes.each do |key, value|
20
+ next unless key.to_s[m = /_attributes$/]
21
+ nested = self.send key.gsub(m, '')
22
+ if nested.respond_to? :each
23
+ self.send(nested).each do |built|
24
+ value.map { |sub_params| built.attributes = sub_params }
25
+ end
26
+ attributes.delete(key)
27
+ else
28
+ nested.attributes = value
29
+ end
30
+ end
31
+ self.attributes = attributes
32
+ save
33
+ end
34
+
35
+ # Hook into models that accept nested attributes.
36
+ module ClassMethods
37
+
38
+ def accepts_nested_attributes_for(field, options={})
39
+
40
+ before_validation do
41
+ return unless @as_user.present?
42
+ child = self.send(field)
43
+
44
+ # Copy down the chain as needed.
45
+ # As_user will be unset on both models after save.
46
+ if child.respond_to? :map
47
+ children = child
48
+ children.map {|c| c.as_user = @as_user }
49
+ else
50
+ child.try(:as_user=, @as_user)
51
+ end
52
+ end
53
+
54
+ super(field, options)
55
+ end
56
+
57
+ end
58
+
59
+ # Add/override statechanging methods to ensure @as_user is unset
60
+ def self.included(base)
61
+ # We need ActiveModel callbacks
62
+ base.extend ActiveModel::Callbacks
63
+
64
+ # Add our stuff
65
+ base.send :attr_accessor, :as_user
66
+ base.extend ClassMethods
67
+
68
+ [:after_save, :after_destroy].each do |m|
69
+ base.send(m) do
70
+ self.as_user = nil
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ ActiveRecord::Base.send :include, ActiveModel::UpdatePermittedAttributes
79
+
80
+ class Hash
81
+
82
+ def all_permitted?
83
+ self.each do |key, value|
84
+ if value.is_a?(Hash)
85
+ return true unless value.respond_to? :all_permitted? # For those without security enabled.
86
+ return false if !value.all_permitted? and !key.numeric?
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,50 @@
1
+ module MixedIdentifierForUserResource
2
+ def has_mixed_identifier_for(user_resource, options={})
3
+
4
+ user_resource = user_resource.to_s
5
+
6
+ self.send :attr_accessible, :"#{user_resource}_identifier"
7
+
8
+ instance_eval <<-END
9
+ validate :"#{user_resource}_identifier", presence: true
10
+ END
11
+
12
+ class_eval <<-END
13
+ def #{user_resource}_identifier=(identifier)
14
+ if identifier.is_a? Fixnum or identifier.numeric? or identifier.parameter_id?
15
+ self.#{user_resource}_id = identifier
16
+ else
17
+ # Try to uncover the as_user of either this resource or the parent
18
+ self.#{user_resource} = #{user_resource.classify}.find_or_initialize_by_email(identifier)
19
+ end
20
+ end
21
+
22
+ def #{user_resource}_identifier
23
+ resource = self.#{user_resource}
24
+ resource.try(:id) || resource.try(:email) || resource.try(:email_address)
25
+ end
26
+
27
+ def mass_assignment_authorizer(role)
28
+ super(role) << "#{user_resource}_identifier"
29
+ end
30
+
31
+ before_validation do
32
+ if #{user_resource}.present? and #{user_resource}.new_record? and #{user_resource}.respond_to? :invite!
33
+ #{user_resource}.invite! @as_user
34
+ self.#{user_resource}_id = #{user_resource}.id
35
+ end
36
+ end
37
+ END
38
+
39
+ end
40
+ end
41
+
42
+ class String
43
+
44
+ # Is the string something like '234234234-Joe-Test'? This is used in params.
45
+ def parameter_id?
46
+ self[/^[0-9]+\-[\D]+/].present?
47
+ end
48
+ end
49
+
50
+ ActiveRecord::Base.extend(MixedIdentifierForUserResource)
@@ -0,0 +1,3 @@
1
+ module ModifyResource
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ require "require_all"
2
+
3
+ $: << File.dirname(__FILE__)
4
+
5
+ require "modify_resource/version"
6
+ require "modify_resource/rails/action_controller/modify_resource"
7
+ require "modify_resource/rails/action_controller/update_as"
8
+ require "modify_resource/rails/active_model/update_permitted_attributes"
9
+ require "modify_resource/rails/active_record/mixed_identifier_for_user_resource"
10
+
11
+ module ModifyResource
12
+
13
+ def self.included(base)
14
+ if defined? Rails and base < ActionController::Base
15
+
16
+ # Add `modify_on` and other methods to the controller
17
+ base.send :include, ActionController::ModifyResource
18
+ base.send :include, ActionController::UpdateAs
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'modify_resource/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "modify_resource"
8
+ gem.version = ModifyResource::VERSION
9
+ gem.authors = ["Colin Young"]
10
+ gem.email = ["me@colinyoung.com"]
11
+ gem.description = %q{This gem makes rails automatic. Securely.}
12
+ gem.summary = %q{stop writing the same controller actions for resources}
13
+ gem.homepage = ""
14
+ gem.add_dependency "require_all"
15
+ gem.add_dependency "rake"
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ["lib"]
21
+ end
@@ -0,0 +1 @@
1
+ require 'test_helper'
File without changes
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: modify_resource
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Colin Young
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: require_all
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: This gem makes rails automatic. Securely.
47
+ email:
48
+ - me@colinyoung.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - lib/modify_resource.rb
59
+ - lib/modify_resource/rails/action_controller/modify_resource.rb
60
+ - lib/modify_resource/rails/action_controller/resource_info.rb
61
+ - lib/modify_resource/rails/action_controller/router.rb
62
+ - lib/modify_resource/rails/action_controller/update_as.rb
63
+ - lib/modify_resource/rails/active_model/update_permitted_attributes.rb
64
+ - lib/modify_resource/rails/active_record/mixed_identifier_for_user_resource.rb
65
+ - lib/modify_resource/version.rb
66
+ - modify_resource.gemspec
67
+ - test/modify_resource_test.rb
68
+ - test/test_helper.rb
69
+ homepage: ''
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.24
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: stop writing the same controller actions for resources
93
+ test_files:
94
+ - test/modify_resource_test.rb
95
+ - test/test_helper.rb