crud_methods 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,9 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .env
6
+ .powrc
7
+ .rspec
8
+ .zeus*
9
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ [ -e .env ] && . .env
2
+ rvm use 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in crude_methods.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 zelig
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.
@@ -0,0 +1,110 @@
1
+ # CrudeMethods
2
+
3
+ This is a rails support library that provides a way to define
4
+ action-dependent controller methods in an order-independent declarative way. The action types default to rails CRUD actions and can be extended or modified on each controller.
5
+
6
+ This gem is supposed to be used by restful/resourceful controller abstractions as a simple and flexible interface for the user to set potentially action-specific behavour.
7
+
8
+ Here I give some end-user cases. Note that this gem does not provide the particular controller abstraction functionalities in the examples, just demonstrate a common use of this specification pattern.
9
+
10
+ 1. a resourceful controller scaffold but adding a search action. The user just hooks into the resource retrieval scope chain, otherwise all behave as a crud index-type action:
11
+
12
+ ```ruby
13
+ class ModelsController
14
+ include ResourfulCrudActions
15
+ crud_actions :defaults, :index! => [:search]
16
+ resources :search do |scope|
17
+ scope.search(params[:search])
18
+ end
19
+ end
20
+ ```
21
+
22
+ 2. Imagine a responder abstraction, then the user can modify action-specific behaviour.
23
+
24
+ ```ruby
25
+ class ModelsController
26
+ include SmartResponder
27
+ crud_actions :defaults
28
+ response_on_success :create do |format|
29
+ format.html { redirect_to home_path }
30
+ end
31
+ end
32
+ ```
33
+
34
+ How these would use `crud_methods` is described in 'Usage' section.
35
+
36
+ ## Features
37
+
38
+ A crud method allows
39
+ - class-level declaration of action specific behaviour
40
+ - the method definition block is executed in controller instance scope when the method is called on the instantiated controller.
41
+ - actions are hierarchically organized allowing default behaviour for an action _type_ (with bang, like `:index!`)
42
+ - user added custom actions will fall back to crud type they belong to
43
+ - specification on different action-types are order-independent say action is `:show`, if current controller specifies a fallback with `:all`, but a specification for `:show!` is inherited, then the latter wins.
44
+ - for a more thorough understanding, see the [documentation of the `state_methods` gem](https://github.com/zelig/state_methods.git).
45
+
46
+ ## Usage
47
+
48
+ This section describes how the developer of any controller abstraction gem will use `crud_methods`.
49
+
50
+ ```ruby
51
+ require 'crud_methods' # require crud_method
52
+ module MyGem::Controller
53
+ def self.included(base)
54
+ base.class_eval do
55
+ include CrudMethods # include crud_method support
56
+ crud_method :resource # declare action-specific method
57
+ # define your action-specific behaviour here on the class level
58
+ resource :create!, :new! do
59
+ resource_class.new(params[model_name], :as => current_role)
60
+ end
61
+ #...
62
+ end
63
+ end
64
+ ```
65
+
66
+ Then the user will do:
67
+ ```ruby
68
+ class SignupsController
69
+ include MyGem::Controller
70
+ crud_actions :defaults, :new! => [:wait]
71
+ # instead of signup just put on waiting list
72
+ resource :wait do
73
+ resource_class.new(params[model_name], :as => current_role).
74
+ tap { status = :waiting }
75
+ end
76
+ end
77
+ ```
78
+
79
+ ## Implementation
80
+
81
+ This gem is basically a paticular use case of the [`state_methods` gem](https://github.com/zelig/state_methods.git) on controller classes where the 'state accessor method' is `action_name` and the default state partition is the usual CRUD actions of rails.
82
+
83
+ ## Gotchas
84
+
85
+ - do not use `super` within the class-level method definition block. it won't refer to anything reliable. use _fallback_ calls. :TODO: add more info.
86
+ - specifying a method for a type (of action), will not override other specs for a subtype (or particular action) either earlier or later. This may be counterintuitive to some especially that specifications for action types can be 'hidden' coming via controller inheritance or included modules. :TODO: add more info.
87
+ - see the [documentation of the `state_methods` gem](https://github.com/zelig/state_methods.git)
88
+
89
+ ## Installation
90
+
91
+ Add this line to your application's Gemfile:
92
+
93
+ gem 'crud_methods'
94
+
95
+ And then execute:
96
+
97
+ $ bundle
98
+
99
+ Or install it yourself as:
100
+
101
+ $ gem install crud_methods
102
+
103
+
104
+ ## Contributing
105
+
106
+ 1. Fork it
107
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
108
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
109
+ 4. Push to the branch (`git push origin my-new-feature`)
110
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'crud_methods/version'
5
+
6
+ DESC = "design pattern that allows order-independent class-level definition of action-dependent controller methods for rails - action types default to rails CRUD actions "
7
+
8
+ Gem::Specification.new do |gem|
9
+ gem.name = "crud_methods"
10
+ gem.version = CrudMethods::VERSION
11
+ gem.authors = ["zelig"]
12
+ gem.email = ["viktor.tron@gmail.com"]
13
+ gem.description = DESC
14
+ gem.summary = DESC
15
+ gem.homepage = "https://github.com/zelig/crud_methods"
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
+
22
+ gem.add_dependency "state_methods"
23
+ gem.add_dependency "rails"
24
+ gem.add_development_dependency 'debugger'
25
+ gem.add_development_dependency "rspec-rails"
26
+
27
+ end
@@ -0,0 +1,6 @@
1
+ require "crud_methods/version"
2
+ require "crud_methods/controller"
3
+
4
+ module CrudMethods
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,50 @@
1
+ require 'state_methods'
2
+ require 'active_support/core_ext/module/attribute_accessors'
3
+
4
+ module CrudMethods
5
+
6
+ ACTIONS = { :C => [:create!, :new!], :U => [:edit!, :update!], :D => [:destroy!], :R => [:show!, :index!] }
7
+ mattr_accessor :actions
8
+ @@actions = ACTIONS
9
+ DEFAULT_ACTIONS = [:new, :create, :edit, :update, :show, :index, :destroy]
10
+ mattr_accessor :default_actions
11
+ @@default_actions = DEFAULT_ACTIONS
12
+ mattr_accessor :default_extension
13
+ @@default_extension = {}
14
+ default_actions.each { |a| default_extension[:"#{a}!"] = a }
15
+
16
+
17
+ module Controller
18
+
19
+ def self.included(base)
20
+ base.class_eval do
21
+ include ::StateMethods
22
+ _state_partition_for :action_name, :partition => ::CrudMethods.actions
23
+ include ControllerInstanceMethods
24
+ extend ControllerClassMethods
25
+ end
26
+ end
27
+
28
+ module ControllerInstanceMethods
29
+
30
+ end
31
+
32
+ module ControllerClassMethods
33
+
34
+ def crud_actions(*args)
35
+ args.each do |spec|
36
+ spec = ::CrudMethods.default_extension if spec == :defaults
37
+ puts spec
38
+ _state_partition_for :action_name, :extend => spec
39
+ end
40
+ puts _state_partition_for(:action_name).index
41
+ end
42
+
43
+ def crud_method(name)
44
+ state_method(name, :action_name)
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module CrudMethods
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,261 @@
1
+ require 'spec_helper'
2
+ require 'crud_methods'
3
+
4
+ require 'rails'
5
+ require 'action_controller/railtie'
6
+ require 'rspec/rails'
7
+ module TestRailsApp
8
+ class Application < Rails::Application
9
+ # app config here
10
+ # config.secret_token = '572c86f5ede338bd8aba8dae0fd3a326aabababc98d1e6ce34b9f5'
11
+ routes.draw do
12
+ resources :models
13
+ get "unknown" => 'anonymous#unknown'
14
+ end
15
+ # routes are created for anonymous controllers https://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller#anonymous-controllers-only-create-resource-routes
16
+ end
17
+
18
+ class ApplicationController < ActionController::Base
19
+ include CrudMethods::Controller
20
+ crud_method :test
21
+ test(:all) { 0 }
22
+ def default_render
23
+ render :text => @content
24
+ end
25
+ end
26
+
27
+ class DefaultTestController < ApplicationController
28
+ extend(RSpec::Rails::ControllerExampleGroup::BypassRescue)
29
+ def self.default_actions
30
+ [:new, :create, :edit, :update, :show, :index, :destroy]
31
+ end
32
+ def self.default_action_types
33
+ default_actions.map { |a| :"#{a}!" }
34
+ end
35
+ default_actions.each do |a|
36
+ define_method(a) {}
37
+ end
38
+ def unknown
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ def default_actions
45
+ TestRailsApp::DefaultTestController.default_actions
46
+ end
47
+
48
+ def default_action_types
49
+ TestRailsApp::DefaultTestController.default_action_types
50
+ end
51
+
52
+ class Error
53
+ def initialize(*args)
54
+ @args = args
55
+ end
56
+ def to_a
57
+ @args
58
+ end
59
+ def to_s
60
+ to_a.join(' with ')
61
+ end
62
+ end
63
+
64
+ def crud_test(action, result, method = :test, *args, &block)
65
+ spec = case result
66
+ when Error
67
+ "when action = #{action} then Controller##{method} raises #{result.to_s}"
68
+ else
69
+ "when action = #{action} then Controller##{method} = #{result}"
70
+ end
71
+ it spec do
72
+ crud_test!(action, result, method, *args, &block)
73
+ end
74
+ end
75
+
76
+ def crud_test!(action, result, method = :test, *args, &block)
77
+ case result
78
+ when Error
79
+ lambda do
80
+ _crud_test(action, result, method, *args, &block)
81
+ end.should raise_error(*result.to_a)
82
+ else
83
+ _crud_test(action, result, method, *args, &block).
84
+ should == result
85
+ end
86
+ end
87
+
88
+ def _crud_test(action, result, method = :test, *args, &block)
89
+ instance_eval(&block) if block_given?
90
+ get action, :id => "anyid"
91
+ puts "controller.action_name: #{controller.action_name}"
92
+ puts "#{controller.class._state_partition_for(:action_name).index}"
93
+ controller.send(method, *args)
94
+ end
95
+
96
+ describe 'CrudMethods' do
97
+
98
+ context "in a Rails controller", :type => :controller do
99
+
100
+ before(:each) do
101
+ @request = ActionController::TestRequest.new
102
+ end
103
+
104
+ describe "default extension" do
105
+
106
+ context "crud method 'test' is set on types only" do
107
+ controller(TestRailsApp::DefaultTestController) do
108
+ crud_actions :defaults
109
+ default_action_types.each { |a| test(a) { a } }
110
+ end
111
+
112
+ it "falls back to :all if action is undeclared" do
113
+ routes.draw { get "unknown" => 'anonymous#unknown' }
114
+ # lambda { get :unknown }.should raise_error(NameError)
115
+ crud_test!(:unknown, 0)
116
+ end
117
+
118
+ default_actions.each do |a|
119
+ crud_test(a, :"#{a}!")
120
+ end
121
+ end
122
+
123
+ context "crud method 'test' is set on actions too and override type spec" do
124
+ controller(TestRailsApp::DefaultTestController) do
125
+ crud_actions :defaults
126
+ default_action_types.each { |a| test(a) { a } }
127
+ default_actions.each { |a| test(a) { a } }
128
+ end
129
+
130
+ it "falls back to :all if action is undeclared" do
131
+ routes.draw { get "unknown" => 'anonymous#unknown' }
132
+ # lambda { get :unknown }.should raise_error(NameError)
133
+ crud_test!(:unknown, 0)
134
+ end
135
+
136
+ default_actions.each do |a|
137
+ crud_test(a, a)
138
+ end
139
+ end
140
+ end
141
+
142
+ describe "alternative extensions" do
143
+
144
+ context ":index! => [:index, :list]" do
145
+
146
+ controller(TestRailsApp::DefaultTestController) do
147
+ crud_actions :index! => [:index, :list]
148
+ default_action_types.each { |a| test(a) { a } }
149
+ def list
150
+ end
151
+ end
152
+
153
+ it "falls back to :all if action is undeclared" do
154
+ routes.draw { get "unknown" => 'anonymous#unknown' }
155
+ # lambda { get :unknown }.should raise_error(NameError)
156
+ crud_test!(:unknown, 0)
157
+ end
158
+
159
+ crud_test(:index, :"index!")
160
+ crud_test(:list, :"index!") { routes.draw { get "list" => 'anonymous#list' } }
161
+ end
162
+
163
+ context ":defaults, :index! => [:index, :list]" do
164
+
165
+ controller(TestRailsApp::DefaultTestController) do
166
+ crud_actions :defaults, :index! => [:index, :list]
167
+ default_action_types.each { |a| test(a) { a } }
168
+ def list
169
+ end
170
+ end
171
+
172
+ it "falls back to :all if action is undeclared" do
173
+ routes.draw { get "unknown" => 'anonymous#unknown' }
174
+ # lambda { get :unknown }.should raise_error(NameError)
175
+ crud_test!(:unknown, 0)
176
+ end
177
+
178
+ default_actions.each do |a|
179
+ crud_test(a, :"#{a}!")
180
+ end
181
+ crud_test(:list, :"index!") { routes.draw { get "list" => 'anonymous#list' } }
182
+ end
183
+
184
+ context ":index => :index!" do
185
+
186
+ it "contoller raises" do
187
+ lambda do Class.new(TestRailsApp::DefaultTestController) do
188
+ crud_actions :index => :index!
189
+ end
190
+ end.should raise_error(::StateMethods::CannotOverrideError, "index!")
191
+ end
192
+ end
193
+
194
+ end
195
+
196
+ context "crud method 'test' set on all crud R and index but not on show" do
197
+ controller(TestRailsApp::DefaultTestController) do
198
+ crud_actions :defaults
199
+ test(:R) { :R }
200
+ test(:index) { :index }
201
+ end
202
+
203
+ it "falls back to :all if action is undeclared" do
204
+ routes.draw { get "unknown" => 'anonymous#unknown' }
205
+ # lambda { get :unknown }.should raise_error(NameError)
206
+ crud_test!(:unknown, 0)
207
+ end
208
+
209
+ crud_test(:show, :R)
210
+ crud_test(:index, :index)
211
+ end
212
+
213
+ context "crud method 'test' passes args correctly" do
214
+ controller(TestRailsApp::DefaultTestController) do
215
+ crud_actions :defaults
216
+ test(:index) { |a, *args| [a, *args] }
217
+ end
218
+
219
+ crud_test(:show, Error.new(ArgumentError, "wrong number of arguments (1 for 0)"), :test, 0)
220
+ crud_test(:show, 0, :test)
221
+ crud_test(:index, [0, 1], :test, 0, 1)
222
+ crud_test(:index, [0], :test, 0)
223
+ crud_test(:index, Error.new(ArgumentError, "wrong number of arguments (0 for 1)"), :test)
224
+ end
225
+
226
+ context "crud method executes in controller instance scope" do
227
+ default = Class.new(TestRailsApp::DefaultTestController) do
228
+ crud_actions :defaults
229
+ crud_method :test1
230
+ crud_method :test2
231
+ test1(:all) { undefined_method1 }
232
+ test2(:all) { undefined_method2 }
233
+ end
234
+
235
+ controller(default) do
236
+ def undefined_method2
237
+ 'defined'
238
+ end
239
+ end
240
+
241
+ crud_test(:show, Error.new(NameError), :test1)
242
+ crud_test(:show, 'defined', :test2)
243
+ end
244
+
245
+ context "crud method delegates to inherited specific state even if superstate is defined in subclass" do
246
+ default = Class.new(TestRailsApp::DefaultTestController) do
247
+ crud_actions :defaults
248
+ test(:index) { 'superclass index' }
249
+ end
250
+
251
+ controller(default) do
252
+ test(:R) { 'subclass R' }
253
+ end
254
+
255
+ crud_test(:index, 'superclass index')
256
+ crud_test(:show, 'subclass R')
257
+ end
258
+
259
+ end
260
+
261
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crud_methods
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - zelig
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: state_methods
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: rails
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
+ - !ruby/object:Gem::Dependency
47
+ name: debugger
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec-rails
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: ! 'design pattern that allows order-independent class-level definition
79
+ of action-dependent controller methods for rails - action types default to rails
80
+ CRUD actions '
81
+ email:
82
+ - viktor.tron@gmail.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - .rvmrc
89
+ - Gemfile
90
+ - LICENSE.txt
91
+ - README.md
92
+ - Rakefile
93
+ - crud_methods.gemspec
94
+ - lib/crud_methods.rb
95
+ - lib/crud_methods/controller.rb
96
+ - lib/crud_methods/version.rb
97
+ - spec/rails_spec.rb
98
+ - spec/spec_helper.rb
99
+ homepage: https://github.com/zelig/crud_methods
100
+ licenses: []
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ segments:
112
+ - 0
113
+ hash: 1289959389357476616
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ segments:
121
+ - 0
122
+ hash: 1289959389357476616
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.24
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: design pattern that allows order-independent class-level definition of action-dependent
129
+ controller methods for rails - action types default to rails CRUD actions
130
+ test_files:
131
+ - spec/rails_spec.rb
132
+ - spec/spec_helper.rb