hookercookerman-merb-resource-scope 0.1.0

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.
Files changed (31) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +297 -0
  3. data/Rakefile +82 -0
  4. data/TODO +3 -0
  5. data/lib/dependencies.rb +1 -0
  6. data/lib/merb-resource-scope.rb +25 -0
  7. data/lib/merb-resource-scope/controller/actions.rb +67 -0
  8. data/lib/merb-resource-scope/controller/helpers.rb +252 -0
  9. data/lib/merb-resource-scope/controller/scoped_resource_mixin.rb +300 -0
  10. data/lib/merb-resource-scope/controller/singleton_actions.rb +25 -0
  11. data/lib/merb-resource-scope/merbtasks.rb +6 -0
  12. data/lib/merb-resource-scope/router/resource_specification.rb +144 -0
  13. data/lib/merb-resource-scope/specification.rb +36 -0
  14. data/spec/app.rb +7 -0
  15. data/spec/controller/_build_enclosing_scope_spec.rb +107 -0
  16. data/spec/controller/scope_resource_mixin_spec.rb +0 -0
  17. data/spec/integration/admin_posts_controller_spec.rb +158 -0
  18. data/spec/integration/campaign_post_comments_controller_spec.rb +158 -0
  19. data/spec/integration/campaign_posts_controller_spec.rb +73 -0
  20. data/spec/integration/campaigns_controller_spec.rb +43 -0
  21. data/spec/integration/images_spec.rb +17 -0
  22. data/spec/integration/myhome_controller_spec.rb +29 -0
  23. data/spec/integration/myhome_posts_comment_controller_spec.rb +44 -0
  24. data/spec/integration/myhome_posts_controller_spec.rb +55 -0
  25. data/spec/integration/profile_images_controller_spec.rb +61 -0
  26. data/spec/integration/tags_controller_spec.rb +34 -0
  27. data/spec/integration/user_posts_controller_spec.rb +71 -0
  28. data/spec/request_with_controller.rb +103 -0
  29. data/spec/spec_helper.rb +33 -0
  30. data/spec/specification_spec.rb +1 -0
  31. metadata +88 -0
@@ -0,0 +1,25 @@
1
+ module MerbResourceScope
2
+ module Controller
3
+ module SingletonActions
4
+
5
+ include Actions
6
+
7
+ def self.included(base)
8
+ base.show_action(:show, :new, :edit, :update, :create, :destroy)
9
+ end
10
+
11
+ undef index
12
+
13
+ def destroy
14
+ self.current_resource = find_resource
15
+ raise Merb::ControllerExceptions::NotFound unless current_resource
16
+ if current_resource.destroy
17
+ redirect_to current_resources_url if enclosing_resource
18
+ else
19
+ raise Merb::ControllerExceptions::BadRequest
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ namespace :merb_resource_scope do
2
+ desc "Do something for merb-resource-scope"
3
+ task :default do
4
+ puts "merb-resources-scope doesn't do anything"
5
+ end
6
+ end
@@ -0,0 +1,144 @@
1
+ module Merb
2
+ class Request
3
+ attr_accessor :resource_specifications
4
+ end
5
+ end
6
+
7
+ # Adds a specification to a route behaviour, using defered blocks
8
+ #
9
+ # Each specification added in a block will be added to the request
10
+ # therefore all specifications within the behaviour block will be
11
+ # added to the request.
12
+ #
13
+ # So what does that mean? when a route is matched, the request will
14
+ # have a list of resource specficiation for that particular route matched
15
+ # which can be used to determine the enclosing resource, which then can
16
+ # used to generate urls like enclosing_resource_url enclosing_resources_url
17
+ #
18
+ # @examples
19
+ # resources_with_specification :comments
20
+ #
21
+ # resource_with_specification :myhome, :spec => {:singleton => true, :find_method => Proc.new{ session.authentication.user}} do
22
+ # resources_with_specification :posts
23
+ # end
24
+ #
25
+ # so what the above just said was this resource is a singleton and I am going to use the proc which is used at the
26
+ # controller to determine how to find that resource
27
+ # @see test-stack/config/routes.rb for more examples
28
+ #
29
+ # options for Specification MUST have the key :spec => {}
30
+ # =======================================================
31
+ #
32
+ #
33
+ # @param options<Hash>
34
+ # :association_method<Symbol>
35
+ #
36
+ # This does what is says, its used to find associations, it is also used for Singleton creations, erh??
37
+ # Well if you have singleton say users/1/profile_image/new, the users specification would be used to create
38
+ # the singleton, ie ProfileImage.new :user => @user, so the association name will be used in this instance!
39
+ #
40
+ # All the deafaults can be set inferred from this, therefore this will be used to set the
41
+ # the resource_name, which is then used to set the class. So setting an association_method :posts
42
+ # would mean resource_name == :post, :klass == Post.
43
+ #
44
+ # :class_name<String>:
45
+ #
46
+ # Used to create resource, simple.
47
+ # This can be inferred from the resource_name therefore if we have a resource_name of :user
48
+ # the class_name would be "User", if you have a class that cannot be inferred from the the resource_name
49
+ # then you need to set this
50
+ #
51
+ # :resource_name<Symbol>:
52
+ #
53
+ # Will be used to create|new resource from the params, i.e :post => {:title => "hello there"}
54
+ # This will be used to create the class_name if not set.
55
+ # NB NB NB NB - This will also be used when generating the url helpers
56
+ # what I mean is that if we have a resource_name "post" the post will be used
57
+ # to generate a named_url i.e admin_post or myhome_post or comment_post OR
58
+ # posts or comment_posts etc actucallly or new_post edit_post etc etc
59
+ #
60
+ # :singelton<TrueClass>: basically says I am not a collection!
61
+ #
62
+ # :find<Symbol, Proc>:
63
+ #
64
+ # A resoure can be found via a specified method or a Proc, the method would need to be
65
+ # accessiable to the controller ie. :current_user
66
+ #
67
+ # :key<Symbol>
68
+ #
69
+ # this is used to find a resource through the params, this will
70
+ # can be inferred from the resource_name ie 'post_id' if a permalink is set
71
+ # then the key is set with that i.e 'login' or 'display_name' etc
72
+ #
73
+ # :permalink<Symbol>
74
+ #
75
+ # is you want to find a resource other then using :id or resource_name_id i.e post_id
76
+ #  then you can set a permalink
77
+ Merb::Router.extensions do
78
+ def resources_with_specification(name, *args, &block)
79
+ raise ArgumentError, "you need specifify at least the name" unless name
80
+ options = args.pop
81
+ spec_options = options && options[:spec] || {}
82
+ spec_options[:name_prefix] = name_prefix_from_options(self)
83
+ options.delete(:spec) if options
84
+ defer(lambda{|request, params|
85
+ request.resource_specifications ||= []
86
+ if scoped_class_name = spec_options[:scoped_class_name]
87
+ spec_options[:class_name] = request.resource_specifications.last.klass_name + scoped_class_name
88
+ end
89
+ request.resource_specifications << MerbResourceScope::Specification.create(name, spec_options)
90
+ params
91
+ }).resources(name, *(args << options), &block)
92
+ end
93
+ alias_method :resources_with_spec, :resources_with_specification
94
+
95
+
96
+ def resource_with_specification(name, *args, &block)
97
+ raise ArgumentError, "you need specifify at least the name" unless name
98
+ options = args.pop || {}
99
+ spec_options = options && options[:spec] || {}
100
+ spec_options[:name_prefix] = name_prefix_from_options(self)
101
+ options.delete(:spec) if options
102
+ spec_options.merge!(:singleton => true)
103
+ defer(lambda{|request, params|
104
+ raise ArgumentError, "you need specifify at least the resource name" unless name
105
+ request.resource_specifications ||= []
106
+ if scoped_class_name = spec_options[:scoped_class_name]
107
+ spec_options[:class_name] = request.resource_specifications.last.klass_name + scoped_class_name
108
+ end
109
+ request.resource_specifications << MerbResourceScope::Specification.create(name, spec_options)
110
+ params
111
+ }).resource(name, *(args << options), &block)
112
+ end
113
+ alias_method :resource_with_spec, :resource_with_specification
114
+
115
+
116
+ # this means that a specification can be added to any route, and they can be chanined together
117
+ # to form a none restful specification
118
+ #
119
+ def specification(association_method, options = {}, &block)
120
+ options[:name_prefix] = name_prefix_from_options(self)
121
+ defer(lambda{|request, params|
122
+ raise ArgumentError, "you need specifify at least the association method" unless association_method
123
+ request.resource_specifications ||= []
124
+ if scoped_class_name = spec_options[:scoped_class_name]
125
+ spec_options[:class_name] = request.resource_specifications.last.klass_name + scoped_class_name
126
+ end
127
+ request.resource_specifications << MerbResourceScope::Specification.create(association_method, options)
128
+ params
129
+ }, &block)
130
+ end
131
+ alias_method :spec, :specification
132
+
133
+ private
134
+ # get the name_prefixes of behaviour so route genentation can happen easily and
135
+ # it does not have to be decleared in the options
136
+ #
137
+ # @param behaviour<Merb::Router::Behavior>
138
+ #
139
+ # @return<Array<String>>
140
+ def name_prefix_from_options(behaviour)
141
+ options = behaviour.instance_variable_get("@options")
142
+ options[:name_prefix] || []
143
+ end
144
+ end
@@ -0,0 +1,36 @@
1
+ # specification for a route
2
+ # the idea of this just to give enough information onto a
3
+ # specified route that enables to know more information
4
+ # about the request then would be normaly be available
5
+ #
6
+ # i.e users/1/posts
7
+ # via attaching specifications to this route
8
+ # we can automagically know "HOW" posts can be found
9
+ # in this case via finding the user and then calling
10
+ # posts on the found user, you know what mean!
11
+
12
+ module MerbResourceScope
13
+ class Specification
14
+ attr_accessor :klass, :resource_name, :find_method, :singleton, :association_method, :foreign_key, :scope_depth, :permalink, :name_prefix, :klass_name
15
+
16
+ def initialize(association_method, options = {})
17
+ @association_method = options[:association_method] && options[:association_method].to_s || association_method.to_s
18
+ @resource_name = options[:resource_name] && options[:resource_name].to_s || Extlib::Inflection.singularize(@association_method)
19
+ @singleton = options[:singleton] || false
20
+ @klass_name = options[:class_name] || Extlib::Inflection.classify(@resource_name)
21
+ @klass = Object.full_const_get(@klass_name) rescue nil
22
+ @find_method = options[:find_method]
23
+ @scope_depth = options[:scope_depth]
24
+ @permalink = options[:permalink] || :id
25
+ @name_prefix = options[:name_prefix]
26
+ @foreign_key = options[:foreign_key] || @permalink == :id ? Extlib::Inflection.foreign_key(@resource_name).to_sym : @permalink
27
+ end
28
+
29
+ # create a specification instance
30
+ # @return <Specification>
31
+ def self.create(association_method, options = {})
32
+ options = options.only(:association_method, :class_name, :singleton, :find_method, :resource_name, :foreign_key, :scope_depth, :permalink, :name_prefix)
33
+ new(association_method, options)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+
2
+ # setup application
3
+
4
+ 20.times {User.gen}
5
+ 22.times {Campaign.gen}
6
+ 20.times {Post.gen}
7
+ 20.times {PostImage.gen}
@@ -0,0 +1,107 @@
1
+ # require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ #
3
+ # given "specifications for [campaign, posts, comments] are available to the controller" do
4
+ # DataMapper.auto_migrate!
5
+ # @campagin_spec = MerbResourceScope::Specification.create(:campaigns)
6
+ # @posts_spec = MerbResourceScope::Specification.create(:posts)
7
+ # @comments_spec = MerbResourceScope::Specification.create(:comments)
8
+ # @specifications = [@campagin_spec, @posts_spec, @comments_spec]
9
+ #
10
+ # @user = User.gen
11
+ # @campaign = Campaign.gen
12
+ # @post = Post.gen :campaign => @campaign, :user => @user
13
+ # @comments = 10.times {Comment.gen :post => @post }
14
+ #
15
+ # @controller = Comments.new(build_request({:campaign_id => "#{@campaign.id}", :post_id => "#{@post.id}"}), 200)
16
+ #
17
+ # @controller.request.resource_specifications = @specifications
18
+ # @controller._call_filters(@controller._before_filters)
19
+ # end
20
+ #
21
+ #
22
+ # describe "ResourceScope", :given => "specifications for [campaign, posts, comments] are available to the controller" do
23
+ #
24
+ # it "should have an enclosing resource of Campaign.get(:id)" do
25
+ # @controller.enclosing_resource.should == Campaign.get(@campaign.id).posts.get(@post.id)
26
+ # end
27
+ #
28
+ # it "should have current_scope of Campaign.get(:id).posts.get(:id).comments" do
29
+ # @controller.resource_scope.current_scope.should == Campaign.get(@campaign.id).posts.get(@post.id).comments
30
+ # end
31
+ #
32
+ # it "should have a new comment when ussng the scope of a campaign " do
33
+ # @controller.resource_scope.current_scope.should == Campaign.get(@campaign.id).posts.get(@post.id).comments
34
+ # end
35
+ # end
36
+ # #
37
+ # given "specifications for [myhome, posts] are available with a controller" do
38
+ # DataMapper.auto_migrate!
39
+ # @myhome_spec = MerbResourceScope::Specification.create(:myhome, :class => User, :find_method => :current_user, :singleton => true)
40
+ # @posts_spec = MerbResourceScope::Specification.create(:posts)
41
+ # @specifications = [@myhome_spec, @posts_spec]
42
+ #
43
+ # @user = User.gen
44
+ # @campaign = Campaign.gen
45
+ # @posts = 10.times {Post.gen :campaign => @campaign, :user => @user}
46
+ #
47
+ # @controller = Comments.new(build_request, 200)
48
+ #
49
+ # @controller.request.resource_specifications = @specifications
50
+ # @controller._call_filters(@controller._before_filters)
51
+ # end
52
+ #
53
+ # describe "ResourceScope", :given => "specifications for [myhome, posts] are available with a controller" do
54
+ # it "should have an enclosing resource of current_user which is User.first for us" do
55
+ # @controller.enclosing_resource.should == User.first
56
+ # end
57
+ #
58
+ # it "should have current_scope of User.first.posts" do
59
+ # @controller.resource_scope.current_scope.should == User.first.posts
60
+ # end
61
+ # end
62
+ # #
63
+ # given "specifications for [myhome, post, :image] are available with a controller" do
64
+ # DataMapper.auto_migrate!
65
+ # @myhome_spec = MerbResourceScope::Specification.create(:myhome, :class => User, :find_method => :current_user, :singleton => true)
66
+ # @posts_spec = MerbResourceScope::Specification.create(:posts)
67
+ # @image_spec = MerbResourceScope::Specification.create(:image, :class => PostImage, :singleton => true)
68
+ # @specifications = [@myhome_spec, @posts_spec, @image_spec]
69
+ #
70
+ # @user = User.gen
71
+ # @campaign = Campaign.gen
72
+ # @post = Post.gen :campaign => @campaign, :user => @user
73
+ # @post_image = PostImage.gen :post => @post, :user => @user
74
+ #
75
+ # @controller = Comments.new(build_request(:post_id => "#{@post.id}"), 200)
76
+ # @controller.request.resource_specifications = @specifications
77
+ # @controller._call_filters(@controller._before_filters)
78
+ # end
79
+ #
80
+ # describe "ResourceScope", :given => "specifications for [myhome, post, :image] are available with a controller" do
81
+ # it "should have an enclosing resource of User.first.posts.get(:id)" do
82
+ # @controller.enclosing_resource.should == User.first.posts.get(@post.id)
83
+ # end
84
+ #
85
+ # it "should have current_scope of User.first.posts.image" do
86
+ # @controller.resource_scope.current_scope.should == User.first.posts.get(@post.id).image
87
+ # end
88
+ # end
89
+ #
90
+ #
91
+ # given "specifications for [:campaigns] are available with a controller" do
92
+ # DataMapper.auto_migrate!
93
+ # @campaigns_spec = MerbResourceScope::Specification.create(:campaigns)
94
+ # @specifications = [@campaigns_spec]
95
+ #
96
+ # @campaign = Campaign.gen
97
+ #
98
+ # @controller = Comments.new(build_request, 200)
99
+ # @controller.request.resource_specifications = @specifications
100
+ # @controller._call_filters(@controller._before_filters)
101
+ # end
102
+ #
103
+ # describe "ResourceScope", :given => "specifications for [:campaigns] are available with a controller" do
104
+ # it "should have current_scope of Campaign" do
105
+ # @controller.resource_scope.current_scope.should == Campaign
106
+ # end
107
+ # end
@@ -0,0 +1,158 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ given "requsting admin/posts" do
4
+ DataMapper.auto_migrate!
5
+ User.gen
6
+ 10.times {Post.gen}
7
+ @rack = request_with_controller("/admin/posts")
8
+ end
9
+ describe "Admin::Post controller spec", :given => "requsting admin/posts" do
10
+ it "should be cool 'requsting requsting admin/posts'" do
11
+ @rack.should be_successful
12
+ end
13
+
14
+ it "should have a current_resources of 'requsting requsting admin/posts'" do
15
+ @rack.controller.current_resources.should == Post.all
16
+ end
17
+
18
+ it "should have content of 'Posts controller, index action'" do
19
+ @rack.body.should contain("Posts controller, index action")
20
+ end
21
+
22
+ it "should have instance variable of posts set" do
23
+ @rack.controller.instance_variable_get("@posts").should_not be_nil
24
+ end
25
+
26
+ describe "route generation" do
27
+ it "should have a current_resource_url of 'admin/posts'" do
28
+ @rack.controller.current_resources_url.should == "/admin/posts"
29
+ end
30
+
31
+ it "should be able to generate a new post url with the admin" do
32
+ @rack.controller.new_current_resource_url.should == "/admin/posts/new"
33
+ end
34
+
35
+ it "should be able to generate a 'recent' custom url" do
36
+ @rack.controller.current_resources_url(:recent).should == "/admin/posts/recent"
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ given "requsting admin/post/new" do
43
+ DataMapper.auto_migrate!
44
+ User.gen
45
+ 10.times {Post.gen}
46
+ @rack = request_with_controller("/admin/posts/new")
47
+ end
48
+
49
+ describe "Admin::Post controller spec", :given => "requsting admin/post/new" do
50
+ it "should be cool 'requsting requsting admin/posts/new'" do
51
+ @rack.should be_successful
52
+ end
53
+
54
+ it "should have content 'Posts controller, new action'" do
55
+ @rack.body.should contain("Posts controller, new action")
56
+ end
57
+
58
+ it "should have an instance variable of @post" do
59
+ @rack.controller.instance_variable_get("@post").should_not be_nil
60
+ end
61
+
62
+ describe "route generation" do
63
+ it "should have a current_resources_url of 'admin/posts'" do
64
+ @rack.controller.current_resources_url.should == "/admin/posts"
65
+ end
66
+ end
67
+ end
68
+ #
69
+ given "requsting admin/post/:id/edit" do
70
+ DataMapper.auto_migrate!
71
+ User.gen
72
+ @post = Post.gen :user => @user
73
+ @rack = request_with_controller("/admin/posts/#{@post.id}/edit")
74
+ end
75
+
76
+ describe "Admin::Post controller spec", :given => "requsting admin/post/:id/edit" do
77
+ it "should be cool 'requsting requsting admin/posts/:id/edit'" do
78
+ @rack.should be_successful
79
+ end
80
+
81
+ it "should have content 'Posts controller, edit action'" do
82
+ @rack.body.should contain("Posts controller, edit action")
83
+ end
84
+
85
+ it "should have a current_resource of Post.get(:id)" do
86
+ @rack.controller.current_resource.should == Post.get(@post.id)
87
+ end
88
+
89
+ describe "route generation" do
90
+ it "should have a current_resource_url of 'admin/posts/:id'" do
91
+ @rack.controller.current_resource_url(@post).should == "/admin/posts/#{@post.id}"
92
+ end
93
+ it "should be able to have a current_resource_url with params if egg='egg'" do
94
+ @rack.controller.current_resource_url(@post, :egg => "egg").should == "/admin/posts/#{@post.id}?egg=egg"
95
+ end
96
+ end
97
+ end
98
+
99
+ #
100
+ given "requsting admin/post with POST" do
101
+ DataMapper.auto_migrate!
102
+ @rack = request_with_controller("/admin/posts", :method => "POST",
103
+ :params => {:post => {:title => "cool", :body => "man"}})
104
+ end
105
+
106
+ describe "Admin::Post controller spec", :given => "requsting admin/post with POST" do
107
+ it "should be cool be cool then redirect to admin/posts" do
108
+ @rack.should redirect_to("/admin/posts")
109
+ end
110
+
111
+ it "should have a created the new post" do
112
+ Post.all.size.should == 1
113
+ end
114
+
115
+ it "should have create a post with a body of 'man'" do
116
+ Post.first(:conditions => {:body => "man"}).should_not be_nil
117
+ end
118
+
119
+ describe "route generation" do
120
+ it "should have a current_resources_url of 'admin/posts'" do
121
+ @rack.controller.current_resources_url.should == "/admin/posts"
122
+ end
123
+ end
124
+ end
125
+
126
+ given "requsting admin/post/:id with PUT" do
127
+ DataMapper.auto_migrate!
128
+ @post = Post.gen
129
+ @rack = request_with_controller("/admin/posts/#{@post.id}", :method => "PUT", :params => {:post => {:title => "whatthe"}})
130
+ end
131
+
132
+ describe "Admin::Post controller spec", :given => "requsting admin/post/:id with PUT" do
133
+ it "should be cool and then 'redirect to admin/posts/:id'" do
134
+ @rack.should redirect_to("/admin/posts/#{@post.id}")
135
+ end
136
+
137
+ it "should have updated the post with its new title" do
138
+ @post.reload.title.should == "whatthe"
139
+ end
140
+ end
141
+ #
142
+ #
143
+ given "requsting admin/post/:id with DELETE" do
144
+ DataMapper.auto_migrate!
145
+ @post = Post.gen
146
+ @rack = request_with_controller("/admin/posts/#{@post.id}", :method => "DELETE")
147
+ end
148
+
149
+ describe "Admin::Post controller spec", :given => "requsting admin/post/:id with DELETE" do
150
+ it "should be cool and then 'redirect to admin/posts'" do
151
+ @rack.should redirect_to("/admin/posts")
152
+ end
153
+
154
+ it "should have deleted the post" do
155
+ Post.all.should be_empty
156
+ end
157
+
158
+ end