thorero-slices 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Fabien Franzen <info@loobmedia.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 ADDED
@@ -0,0 +1,102 @@
1
+ Merb-Slices
2
+ ===========
3
+
4
+ Merb-Slices is a Merb plugin for using and creating application 'slices' which
5
+ help you modularize your application. Usually these are reuseable extractions
6
+ from your main app. In effect, a Slice is just like a regular Merb MVC
7
+ application, both in functionality as well as in structure.
8
+
9
+ When you generate a Slice stub structure, a module is setup to serve as a
10
+ namespace for your controller, models, helpers etc. This ensures maximum
11
+ encapsulation. You could say a Slice is a mixture between a Merb plugin (a
12
+ Gem) and a Merb application, reaping the benefits of both.
13
+
14
+ A host application can 'mount' a Slice inside the router, which means you have
15
+ full over control how it integrates. By default a Slice's routes are prefixed
16
+ by its name (a router :namespace), but you can easily provide your own prefix
17
+ or leave it out, mounting it at the root of your url-schema. You can even
18
+ mount a Slice multiple times and give extra parameters to customize an
19
+ instance's behaviour.
20
+
21
+ A Slice's Application controller uses controller_for_slice to setup slice
22
+ specific behaviour, which mainly affects cascaded view handling. Additionaly,
23
+ this method is available to any kind of controller, so it can be used for
24
+ Merb Mailer too for example.
25
+
26
+ There are many ways which let you customize a Slice's functionality and
27
+ appearance without ever touching the Gem-level code itself. It's not only easy
28
+ to add template/layout overrides, you can also add/modify controllers, models
29
+ and other runtime code from within the host application.
30
+
31
+ To create your own Slice run this (somewhere outside of your merb app):
32
+
33
+ $ merb-gen slice <your-lowercase-slice-name>
34
+
35
+ ------------------------------------------------------------------------------
36
+
37
+ Instructions for installation of an imaginary 'BlogSlice' slice:
38
+
39
+ file: config/init.rb
40
+
41
+ # add the slice as a regular dependency
42
+
43
+ dependency 'blog-slice'
44
+
45
+ # if needed, configure which slices to load and in which order
46
+
47
+ Merb::Plugins.config[:merb_slices] = { :queue => ["BlogSlice", ...] }
48
+
49
+ # optionally configure the plugins in a before_app_loads callback
50
+
51
+ Merb::BootLoader.before_app_loads do
52
+
53
+ Merb::Slices::config[:blog_slice] = { ... }
54
+
55
+ end
56
+
57
+ file: config/router.rb
58
+
59
+ # example: /blog-slice/:controller/:action/:id
60
+
61
+ r.add_slice(:BlogSlice)
62
+
63
+ # example: /foo/:controller/:action/:id
64
+
65
+ r.add_slice(:BlogSlice, 'foo') # same as :path => 'foo'
66
+
67
+ # example: /:lang/:controller/:action/:id (with :a param set)
68
+
69
+ r.add_slice(:BlogSlice, :path => ':lang', :params => { :a => 'b' })
70
+
71
+ # example: /:controller/:action/:id
72
+
73
+ r.slice(:BlogSlice)
74
+
75
+ Normally you should also run the following rake task:
76
+
77
+ rake slices:blog_slice:install
78
+
79
+ ------------------------------------------------------------------------------
80
+
81
+ You can put your application-level overrides in:
82
+
83
+ host-app/slices/blog-slice/app - controllers, models, views ...
84
+
85
+ Templates are located in this order:
86
+
87
+ 1. host-app/slices/blog-slice/app/views/*
88
+ 2. gems/blog-slice/app/views/*
89
+ 3. host-app/app/views/*
90
+
91
+ You can use the host application's layout by configuring the
92
+ blog-slice slice in a before_app_loads block:
93
+
94
+ Merb::Slices.config[:blog_slice] = { :layout => :application }
95
+
96
+ By default :blog_slice is used. If you need to override
97
+ stylesheets or javascripts, just specify your own files in your layout
98
+ instead/in addition to the ones supplied (if any) in
99
+ host-app/public/slices/blog-slice.
100
+
101
+ In any case don't edit those files directly as they may be clobbered any time
102
+ rake blog_slice:install is run.
@@ -0,0 +1,65 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require "extlib"
4
+ require 'merb-core/tasks/merb_rake_helper'
5
+ require "spec/rake/spectask"
6
+
7
+ ##############################################################################
8
+ # Package && release
9
+ ##############################################################################
10
+ RUBY_FORGE_PROJECT = "thorero"
11
+ PROJECT_URL = "http://merbivore.com"
12
+ PROJECT_SUMMARY = "Merb-Slices is a Merb plugin for using and creating application 'slices' which help you modularize your application."
13
+ PROJECT_DESCRIPTION = PROJECT_SUMMARY
14
+
15
+ GEM_AUTHOR = "Fabien Franzen"
16
+ GEM_EMAIL = "info@fabien.be"
17
+
18
+ GEM_NAME = "thorero-slices"
19
+ PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
20
+ GEM_VERSION = (Merb::MORE_VERSION rescue "0.9.4") + PKG_BUILD
21
+
22
+ RELEASE_NAME = "REL #{GEM_VERSION}"
23
+
24
+ require "extlib/tasks/release"
25
+
26
+ spec = Gem::Specification.new do |s|
27
+ s.rubyforge_project = RUBY_FORGE_PROJECT
28
+ s.name = GEM_NAME
29
+ s.version = GEM_VERSION
30
+ s.platform = Gem::Platform::RUBY
31
+ s.has_rdoc = true
32
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
33
+ s.summary = PROJECT_SUMMARY
34
+ s.description = PROJECT_DESCRIPTION
35
+ s.author = GEM_AUTHOR
36
+ s.email = GEM_EMAIL
37
+ s.homepage = PROJECT_URL
38
+ s.add_dependency('merb-core', '>= 0.9.4')
39
+ s.require_path = 'lib'
40
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec,merb_generators}/**/*")
41
+ end
42
+
43
+ Rake::GemPackageTask.new(spec) do |pkg|
44
+ pkg.gem_spec = spec
45
+ end
46
+
47
+ desc "Install the gem"
48
+ task :install => [:package] do
49
+ sh %{#{sudo} gem install #{install_home} pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}
50
+ end
51
+
52
+ namespace :jruby do
53
+
54
+ desc "Run :package and install the resulting .gem with jruby"
55
+ task :install => :package do
56
+ sh %{#{sudo} jruby -S gem install #{install_home} pkg/#{GEM_NAME}-#{GEM_VERSION}.gem --no-rdoc --no-ri}
57
+ end
58
+
59
+ end
60
+
61
+ desc "Run all specs"
62
+ Spec::Rake::SpecTask.new("specs") do |t|
63
+ t.spec_opts = ["--format", "specdoc", "--colour"]
64
+ t.spec_files = Dir["spec/**/*_spec.rb"].sort
65
+ end
data/TODO ADDED
File without changes
@@ -0,0 +1,126 @@
1
+ require 'merb-slices/module'
2
+
3
+ if defined?(Merb::Plugins)
4
+
5
+ Merb::Plugins.add_rakefiles "merb-slices/merbtasks"
6
+
7
+ Merb::Plugins.config[:merb_slices] ||= {}
8
+
9
+ require "merb-slices/module_mixin"
10
+ require "merb-slices/controller_mixin"
11
+ require "merb-slices/router_ext"
12
+
13
+ # Enable slice behaviour for any class inheriting from AbstractController.
14
+ # To use this call controller_for_slice in your controller class.
15
+ Merb::AbstractController.send(:include, Merb::Slices::ControllerMixin)
16
+
17
+ # Load Slice classes before the app's classes are loaded.
18
+ #
19
+ # This allows the application to override/merge any slice-level classes.
20
+ class Merb::Slices::Loader < Merb::BootLoader
21
+
22
+ before LoadClasses
23
+
24
+ class << self
25
+
26
+ # Gather all slices from search path and gems and load their classes.
27
+ def run
28
+ Merb::Slices.register_slices_from_search_path! if auto_register?
29
+ Merb::Slices.each_slice { |slice| slice.load_slice }
30
+ end
31
+
32
+ # Load a single file and its requirements.
33
+ #
34
+ # @param file<String> The file to load.
35
+ def load_file(file)
36
+ Merb::BootLoader::LoadClasses.load_file file
37
+ end
38
+
39
+ # Remove a single file and the classes loaded by it from ObjectSpace.
40
+ #
41
+ # @param file<String> The file to load.
42
+ def remove_file(file)
43
+ Merb::BootLoader::LoadClasses.remove_file file
44
+ end
45
+
46
+ # Load classes from given paths - using path/glob pattern.
47
+ #
48
+ # @param *paths <Array> Array of paths to load classes from - may contain glob pattern
49
+ def load_classes(*paths)
50
+ Merb::BootLoader::LoadClasses.load_classes paths
51
+ end
52
+
53
+ # Reload the router - takes all_slices into account to load slices at runtime.
54
+ def reload_router!
55
+ Merb::BootLoader::LoadClasses.reload_router!
56
+ end
57
+
58
+ # Slice-level paths for all loaded slices.
59
+ #
60
+ # @return <Array[String]> Any slice-level paths that have been loaded.
61
+ def slice_paths
62
+ paths = []
63
+ Merb::Slices.each_slice { |slice| paths += slice.collected_slice_paths }
64
+ paths
65
+ end
66
+
67
+ # App-level paths for all loaded slices.
68
+ #
69
+ # @return <Array[String]> Any app-level paths that have been loaded.
70
+ def app_paths
71
+ paths = []
72
+ Merb::Slices.each_slice { |slice| paths += slice.collected_app_paths }
73
+ paths
74
+ end
75
+
76
+ private
77
+
78
+ # Whether slices from search paths should be registered automatically.
79
+ # Defaults to true if not explicitly set.
80
+ def auto_register?
81
+ Merb::Plugins.config[:merb_slices][:auto_register] || !Merb::Plugins.config[:merb_slices].key?(:auto_register)
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ # Call initialization method for each registered Slice.
89
+ #
90
+ # This is done just before the app's after_load_callbacks are run.
91
+ # The application has been practically loaded completely, letting
92
+ # the callbacks work with what has been loaded.
93
+ class Merb::Slices::Initialize < Merb::BootLoader
94
+
95
+ before AfterAppLoads
96
+
97
+ def self.run
98
+ Merb::Slices.each_slice do |slice|
99
+ Merb.logger.info!("Initializing slice '#{slice}' ...")
100
+ slice.init if slice.respond_to?(:init)
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ # Call activation method for each registered Slice.
107
+ #
108
+ # This is done right after the app's after_load_callbacks are run.
109
+ # Any settings can be taken into account in the activation step.
110
+ #
111
+ # @note Activation will only take place if the slice has been routed;
112
+ # this means you need have at least one slice route setup.
113
+ class Merb::Slices::Activate < Merb::BootLoader
114
+
115
+ after AfterAppLoads
116
+
117
+ def self.run
118
+ Merb::Slices.each_slice do |slice|
119
+ Merb.logger.info!("Activating slice '#{slice}' ...")
120
+ slice.activate if slice.respond_to?(:activate) && slice.routed?
121
+ end
122
+ end
123
+
124
+ end
125
+
126
+ end
@@ -0,0 +1,129 @@
1
+ module Merb
2
+ module Slices
3
+
4
+ module ControllerMixin
5
+
6
+ def self.included(klass)
7
+ klass.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # Setup a controller to reference a slice and its template roots
13
+ #
14
+ # This method is available to any class inheriting from Merb::AbstractController;
15
+ # it enabled correct location of templates, as well as access to the slice module.
16
+ #
17
+ # @param slice_module<#to_s> The slice module to use; defaults to current module.
18
+ # @param options<Hash>
19
+ # Optional parameters to set which component path is used (defaults to :view) and
20
+ # the :path option lets you specify a subdirectory of that component path.
21
+ # When :layout is set, then this is used instead of the config's :layout setting.
22
+ #
23
+ # @example controller_for_slice # uses current module
24
+ # @example controller_for_slice SliceMod # defaults to :view templates and no subdirectory
25
+ # @example controller_for_slice :templates_for => :mailer, :path => 'views' # for Merb::Mailer
26
+ # @example controller_for_slice SliceMod, :templates_for => :mailer, :path => 'views' # for Merb::Mailer
27
+ def controller_for_slice(slice_module = nil, options = {})
28
+ options, slice_module = slice_module.merge(options), nil if slice_module.is_a?(Hash)
29
+ slice_module ||= self.name.split('::').first
30
+ options[:templates_for] = :view unless options.key?(:templates_for)
31
+ if slice_mod = Merb::Slices[slice_module.to_s]
32
+ # Include the instance methods
33
+ unless self.kind_of?(Merb::Slices::ControllerMixin::MixinMethods)
34
+ self.send(:extend, Merb::Slices::ControllerMixin::MixinMethods)
35
+ end
36
+ # Reference this controller's slice module
37
+ self.class_inheritable_accessor :slice
38
+ self.slice = slice_mod
39
+ # Setup template roots
40
+ if options[:templates_for]
41
+ self._template_root = join_template_path(slice_mod.dir_for(options[:templates_for]), options[:path])
42
+ self._template_roots = []
43
+ # app-level app/views directory for shared and fallback views, layouts and partials
44
+ self._template_roots << [join_template_path(Merb.dir_for(options[:templates_for]), options[:path]), :_template_location] if Merb.dir_for(options[:templates_for])
45
+ # slice-level app/views for the standard supplied views
46
+ self._template_roots << [self._template_root, :_slice_template_location]
47
+ # app-level slices/<slice>/app/views for specific overrides
48
+ self._template_roots << [join_template_path(slice_mod.app_dir_for(options[:templates_for]), options[:path]), :_slice_template_location]
49
+ # additional template roots for specific overrides (optional)
50
+ self._template_roots += Array(options[:template_roots]) if options[:template_roots]
51
+ end
52
+ # Set the layout for this slice controller
53
+ layout_for_slice(options[:layout])
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def join_template_path(*segments)
60
+ File.join(*segments.compact)
61
+ end
62
+
63
+ end
64
+
65
+ module MixinMethods
66
+
67
+ def self.extended(klass)
68
+ klass.send(:include, InstanceMethods)
69
+ klass.hide_action :slice
70
+ end
71
+
72
+ # Use the slice's layout - defaults to underscored identifier.
73
+ #
74
+ # This is set for generated stubs that support layouts.
75
+ #
76
+ # @param layout<#to_s> The layout name to use.
77
+ def layout_for_slice(layout = nil)
78
+ layout(layout || self.slice.config[:layout]) if layout || self.slice.config.key?(:layout)
79
+ end
80
+
81
+ module InstanceMethods
82
+
83
+ # Reference this controller's slice module directly.
84
+ #
85
+ # @return <Module> A slice module.
86
+ def slice; self.class.slice; end
87
+
88
+ # Generate a url - takes the slice's :path_prefix into account.
89
+ def slice_url(name, rparams={})
90
+ self.slice.url(name, rparams, {
91
+ :controller => controller_name,
92
+ :action => action_name,
93
+ :format => params[:format]
94
+ })
95
+ end
96
+
97
+ private
98
+
99
+ # This is called after the controller is instantiated to figure out where to
100
+ # for templates under the _template_root. This helps the controllers
101
+ # of a slice to locate templates without looking in a subdirectory with
102
+ # the name of the module. Instead it will just be app/views/controller/*
103
+ #
104
+ # @param context<#to_str> The controller context (the action or template name).
105
+ # @param type<#to_str> The content type. Defaults to nil.
106
+ # @param controller<#to_str> The name of the controller. Defaults to controller_name.
107
+ #
108
+ # @return <String>
109
+ # Indicating where to look for the template for the current controller,
110
+ # context, and content-type.
111
+ def _slice_template_location(context, type = nil, controller = controller_name)
112
+ if controller && controller.include?('/')
113
+ # skip first segment if given (which is the module name)
114
+ segments = controller.split('/')
115
+ "#{segments[1,segments.length-1].join('/')}/#{context}.#{type}"
116
+ else
117
+ # default template location logic
118
+ _template_location(context, type, controller)
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+ end