hookercookerman-merb-resource-scope 0.1.0

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