teamon-merb-auth-slice-activation 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +198 -0
  3. data/Rakefile +57 -0
  4. data/TODO +9 -0
  5. data/app/controllers/activations.rb +24 -0
  6. data/app/controllers/application.rb +5 -0
  7. data/app/helpers/application_helper.rb +64 -0
  8. data/app/helpers/mailer_helper.rb +29 -0
  9. data/app/mailers/activation_mailer.rb +18 -0
  10. data/app/mailers/views/activation_mailer/activation.text.erb +1 -0
  11. data/app/mailers/views/activation_mailer/signup.text.erb +7 -0
  12. data/app/views/layout/merb_auth_slice_activation.html.erb +16 -0
  13. data/lib/merb-auth-slice-activation.rb +91 -0
  14. data/lib/merb-auth-slice-activation/merbtasks.rb +112 -0
  15. data/lib/merb-auth-slice-activation/mixins/activated_user.rb +118 -0
  16. data/lib/merb-auth-slice-activation/mixins/activated_user/ar_activated_user.rb +21 -0
  17. data/lib/merb-auth-slice-activation/mixins/activated_user/dm_activated_user.rb +24 -0
  18. data/lib/merb-auth-slice-activation/mixins/activated_user/sq_activated_user.rb +23 -0
  19. data/lib/merb-auth-slice-activation/slicetasks.rb +18 -0
  20. data/lib/merb-auth-slice-activation/spectasks.rb +75 -0
  21. data/public/javascripts/master.js +0 -0
  22. data/public/stylesheets/master.css +2 -0
  23. data/spec/controllers/activations_spec.rb +83 -0
  24. data/spec/mailers/activation_mailer_spec.rb +68 -0
  25. data/spec/mixins/activated_user_spec.rb +122 -0
  26. data/spec/spec_helper.rb +61 -0
  27. data/stubs/app/controllers/activations.rb +8 -0
  28. data/stubs/app/mailers/views/activation_mailer/activation.text.erb +1 -0
  29. data/stubs/app/mailers/views/activation_mailer/signup.text.erb +7 -0
  30. metadata +138 -0
@@ -0,0 +1,16 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5
+ <title>Fresh MerbAuthSliceActivation Slice</title>
6
+ <link href="<%= public_path_for :stylesheet, 'master.css' %>" type="text/css" charset="utf-8" rel="stylesheet" media="all" />
7
+ <script src="<%= public_path_for :javascript, 'master.js' %>" type="text/javascript" charset="utf-8"></script>
8
+ </head>
9
+ <!-- you can override this layout at slices/merb-auth-slice-activation/app/views/layout/merb-auth-slice-activation.html.erb -->
10
+ <body class="merb-auth-slice-activation-slice">
11
+ <div id="container">
12
+ <h1>MerbAuthSliceActivation Slice</h1>
13
+ <div id="main"><%= catch_content :for_layout %></div>
14
+ </div>
15
+ </body>
16
+ </html>
@@ -0,0 +1,91 @@
1
+ if defined?(Merb::Plugins)
2
+
3
+ $:.unshift File.dirname(__FILE__)
4
+
5
+ dependency 'merb-slices'
6
+ dependency 'merb-auth-core'
7
+ dependency 'merb-auth-more'
8
+ dependency 'merb-mailer'
9
+ require(File.expand_path(File.dirname(__FILE__) / "merb-auth-slice-activation" / "mixins") / "activated_user")
10
+
11
+ Merb::Plugins.add_rakefiles "merb-auth-slice-activation/merbtasks", "merb-auth-slice-activation/slicetasks", "merb-auth-slice-activation/spectasks"
12
+
13
+ # Register the Slice for the current host application
14
+ Merb::Slices::register(__FILE__)
15
+
16
+ # Slice configuration - set this in a before_app_loads callback.
17
+ # By default a Slice uses its own layout, so you can swicht to
18
+ # the main application layout or no layout at all if needed.
19
+ #
20
+ # Configuration options:
21
+ # :layout - the layout to use; defaults to :merb-auth-slice-activation
22
+ # :mirror - which path component types to use on copy operations; defaults to all
23
+ Merb::Slices::config[:merb_auth_slice_activation][:layout] ||= :application
24
+
25
+ # All Slice code is expected to be namespaced inside a module
26
+ module MerbAuthSliceActivation
27
+
28
+ # Slice metadata
29
+ self.description = "MerbAuthSliceActivation is a merb slice that adds basic activation for merb-auth-based merb applications."
30
+ self.version = "1.0.1"
31
+ self.author = "Daniel Neighman, Christian Kebekus"
32
+
33
+ # Stub classes loaded hook - runs before LoadClasses BootLoader
34
+ # right after a slice's classes have been loaded internally.
35
+ def self.loaded
36
+ end
37
+
38
+ # Initialization hook - runs before AfterAppLoads BootLoader
39
+ def self.init
40
+ # Actually check if the user is active
41
+ ::Merb::Authentication.after_authentication do |user, *rest|
42
+ if user.respond_to?(:active?)
43
+ user.active? ? user : nil
44
+ else
45
+ user
46
+ end
47
+ end
48
+ end
49
+
50
+ # Activation hook - runs after AfterAppLoads BootLoader
51
+ def self.activate
52
+ end
53
+
54
+ # Deactivation hook - triggered by Merb::Slices.deactivate(MerbAuthSliceActivation)
55
+ def self.deactivate
56
+ end
57
+
58
+ # Setup routes inside the host application
59
+ #
60
+ # @param scope<Merb::Router::Behaviour>
61
+ # Routes will be added within this scope (namespace). In fact, any
62
+ # router behaviour is a valid namespace, so you can attach
63
+ # routes at any level of your router setup.
64
+ #
65
+ # @note prefix your named routes with :merb_auth_slice_activation_
66
+ # to avoid potential conflicts with global named routes.
67
+ def self.setup_router(scope)
68
+ scope.match("/activate/:activation_code").to(:controller => "activations", :action => "activate").name(:activate)
69
+ end
70
+
71
+ end
72
+
73
+ # Setup the slice layout for MerbAuthSliceActivation
74
+ #
75
+ # Use MerbAuthSliceActivation.push_path and MerbAuthSliceActivation.push_app_path
76
+ # to set paths to merb-auth-slice-activation-level and app-level paths. Example:
77
+ #
78
+ # MerbAuthSliceActivation.push_path(:application, MerbAuthSliceActivation.root)
79
+ # MerbAuthSliceActivation.push_app_path(:application, Merb.root / 'slices' / 'merb-auth-slice-activation')
80
+ # ...
81
+ #
82
+ # Any component path that hasn't been set will default to MerbAuthSliceActivation.root
83
+ #
84
+ # Or just call setup_default_structure! to setup a basic Merb MVC structure.
85
+ MerbAuthSliceActivation.setup_default_structure!
86
+ MaSA = MerbAuthSliceActivation unless defined?(MaSA)
87
+
88
+ # Add dependencies for other MerbAuthSliceActivation classes below. Example:
89
+ # dependency "merb-auth-slice-activation/other"
90
+
91
+ end
@@ -0,0 +1,112 @@
1
+ namespace :slices do
2
+ namespace :merb_auth_slice_activation do
3
+
4
+ desc "Install MerbAuthSliceActivation"
5
+ task :install => [:preflight, :setup_directories, :copy_assets, :migrate]
6
+
7
+ desc "Test for any dependencies"
8
+ task :preflight do # see slicetasks.rb
9
+ end
10
+
11
+ desc "Setup directories"
12
+ task :setup_directories do
13
+ puts "Creating directories for host application"
14
+ MerbAuthSliceActivation.mirrored_components.each do |type|
15
+ if File.directory?(MerbAuthSliceActivation.dir_for(type))
16
+ if !File.directory?(dst_path = MerbAuthSliceActivation.app_dir_for(type))
17
+ relative_path = dst_path.relative_path_from(Merb.root)
18
+ puts "- creating directory :#{type} #{File.basename(Merb.root) / relative_path}"
19
+ mkdir_p(dst_path)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ desc "Copy stub files to host application"
26
+ task :stubs do
27
+ puts "Copying stubs for MerbAuthSliceActivation - resolves any collisions"
28
+ copied, preserved = MerbAuthSliceActivation.mirror_stubs!
29
+ puts "- no files to copy" if copied.empty? && preserved.empty?
30
+ copied.each { |f| puts "- copied #{f}" }
31
+ preserved.each { |f| puts "! preserved override as #{f}" }
32
+ end
33
+
34
+ desc "Copy stub files and views to host application"
35
+ task :patch => [ "stubs", "freeze:views" ]
36
+
37
+ desc "Copy public assets to host application"
38
+ task :copy_assets do
39
+ puts "Copying assets for MerbAuthSliceActivation - resolves any collisions"
40
+ copied, preserved = MerbAuthSliceActivation.mirror_public!
41
+ puts "- no files to copy" if copied.empty? && preserved.empty?
42
+ copied.each { |f| puts "- copied #{f}" }
43
+ preserved.each { |f| puts "! preserved override as #{f}" }
44
+ end
45
+
46
+ desc "Migrate the database"
47
+ task :migrate do # see slicetasks.rb
48
+ end
49
+
50
+ desc "Freeze MerbAuthSliceActivation into your app (only merb-auth-slice-activation/app)"
51
+ task :freeze => [ "freeze:app" ]
52
+
53
+ namespace :freeze do
54
+
55
+ desc "Freezes MerbAuthSliceActivation by installing the gem into application/gems"
56
+ task :gem do
57
+ ENV["GEM"] ||= "merb-auth-slice-activation"
58
+ Rake::Task['slices:install_as_gem'].invoke
59
+ end
60
+
61
+ desc "Freezes MerbAuthSliceActivation by copying all files from merb-auth-slice-activation/app to your application"
62
+ task :app do
63
+ puts "Copying all merb-auth-slice-activation/app files to your application - resolves any collisions"
64
+ copied, preserved = MerbAuthSliceActivation.mirror_app!
65
+ puts "- no files to copy" if copied.empty? && preserved.empty?
66
+ copied.each { |f| puts "- copied #{f}" }
67
+ preserved.each { |f| puts "! preserved override as #{f}" }
68
+ end
69
+
70
+ desc "Freeze all views into your application for easy modification"
71
+ task :views do
72
+ puts "Copying all view templates to your application - resolves any collisions"
73
+ copied, preserved = MerbAuthSliceActivation.mirror_files_for :view
74
+ puts "- no files to copy" if copied.empty? && preserved.empty?
75
+ copied.each { |f| puts "- copied #{f}" }
76
+ preserved.each { |f| puts "! preserved override as #{f}" }
77
+ end
78
+
79
+ desc "Freeze all mailers into your application for easy modification"
80
+ task :mailers do
81
+ puts "Copying all mailer templates to your application - resolves any collisions"
82
+ copied, preserved = MerbAuthSliceActivation.mirror_files_for :mailer
83
+ puts "- no files to copy" if copied.empty? && preserved.empty?
84
+ copied.each { |f| puts "- copied #{f}" }
85
+ preserved.each { |f| puts "! preserved override as #{f}" }
86
+ end
87
+
88
+ desc "Freeze all models into your application for easy modification"
89
+ task :models do
90
+ puts "Copying all models to your application - resolves any collisions"
91
+ copied, preserved = MerbAuthSliceActivation.mirror_files_for :model
92
+ puts "- no files to copy" if copied.empty? && preserved.empty?
93
+ copied.each { |f| puts "- copied #{f}" }
94
+ preserved.each { |f| puts "! preserved override as #{f}" }
95
+ end
96
+
97
+ desc "Freezes MerbAuthSliceActivation as a gem and copies over merb-auth-slice-activation/app"
98
+ task :app_with_gem => [:gem, :app]
99
+
100
+ desc "Freezes MerbAuthSliceActivation by unpacking all files into your application"
101
+ task :unpack do
102
+ puts "Unpacking MerbAuthSliceActivation files to your application - resolves any collisions"
103
+ copied, preserved = MerbAuthSliceActivation.unpack_slice!
104
+ puts "- no files to copy" if copied.empty? && preserved.empty?
105
+ copied.each { |f| puts "- copied #{f}" }
106
+ preserved.each { |f| puts "! preserved override as #{f}" }
107
+ end
108
+
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,118 @@
1
+ module Merb
2
+ class Authentication
3
+ module Mixins
4
+ # This mixin provides basic user activation.
5
+ #
6
+ # Added properties:
7
+ # :activated_at, DateTime
8
+ # :activation_code, String
9
+ #
10
+ # To use it simply require it and include it into your user class.
11
+ #
12
+ # class User
13
+ # include Authentication::Mixins::ActivatedUser
14
+ #
15
+ # end
16
+ #
17
+ module ActivatedUser
18
+ def self.included(base)
19
+ base.class_eval do
20
+ include Merb::Authentication::Mixins::ActivatedUser::InstanceMethods
21
+ extend Merb::Authentication::Mixins::ActivatedUser::ClassMethods
22
+
23
+ path = File.expand_path(File.dirname(__FILE__)) / "activated_user"
24
+ if defined?(DataMapper) && DataMapper::Resource > self
25
+ require path / "dm_activated_user"
26
+ extend(Merb::Authentication::Mixins::ActivatedUser::DMClassMethods)
27
+ elsif defined?(ActiveRecord) && ancestors.include?(ActiveRecord::Base)
28
+ require path / "ar_activated_user"
29
+ extend(Merb::Authentication::Mixins::ActivatedUser::ARClassMethods)
30
+ elsif defined?(Sequel) && ancestors.include?(Sequel::Model)
31
+ require path / "sq_activated_user"
32
+ extend(Merb::Authentication::Mixins::ActivatedUser::SQClassMethods)
33
+ end
34
+
35
+ end # base.class_eval
36
+ end # self.included
37
+
38
+
39
+ module ClassMethods
40
+ # Create random key.
41
+ #
42
+ # ==== Returns
43
+ # String:: The generated key
44
+ def make_key
45
+ Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
46
+ end
47
+ end # ClassMethods
48
+
49
+ module InstanceMethods
50
+
51
+ # Activates and saves the user.
52
+ def activate!
53
+ self.reload unless self.new_record? # Make sure the model is up to speed before we try to save it
54
+ set_activated_data!
55
+ self.save
56
+
57
+ # send mail for activation
58
+ send_activation_notification
59
+ end
60
+
61
+ # Checks if the user has just been activated. Where 'just' means within the current request.
62
+ # Note that a user can be activate, but the method returns +false+!
63
+ #
64
+ # ==== Returns
65
+ # Boolean:: +true+ is the user has been activated, otherwise +false+.
66
+ def recently_activated?
67
+ @activated
68
+ end
69
+
70
+ # Checks to see if a user is active
71
+ def active?
72
+ return false if self.new_record?
73
+ !! activation_code.nil?
74
+ end
75
+
76
+ alias_method :activated?, :active?
77
+
78
+ # Creates and sets the activation code for the user.
79
+ #
80
+ # ==== Returns
81
+ # String:: The activation code.
82
+ def make_activation_code
83
+ self.activation_code = self.class.make_key
84
+ end
85
+
86
+ # Sends out the activation notification.
87
+ # Used 'Welcome' as subject if +MaSA[:activation_subject]+ is not set.
88
+ def send_activation_notification
89
+ deliver_activation_email(:activation, :subject => (MaSA[:welcome_subject] || "Welcome" ))
90
+ end
91
+
92
+ # Sends out the signup notification.
93
+ # Used 'Please Activate Your Account' as subject if +MaSA[:activation_subject]+ is not set.
94
+ def send_signup_notification
95
+ deliver_activation_email(:signup, :subject => (MaSA[:activation_subject] || "Please Activate Your Account") )
96
+ end
97
+
98
+ private
99
+
100
+ # Helper method delivering the email.
101
+ def deliver_activation_email(action, params)
102
+ from = MaSA[:from_email]
103
+ raise "No :from_email option set for Merb::Slices::config[:merb_auth_slice_activation][:from_email]" unless from
104
+ MaSA::ActivationMailer.dispatch_and_deliver(action, params.merge(:from => from, :to => self.email), :user => self)
105
+ end
106
+
107
+ def set_activated_data!
108
+ @activated = true
109
+ self.activated_at = DateTime.now
110
+ self.activation_code = nil
111
+ true
112
+ end
113
+
114
+ end # InstanceMethods
115
+ end # ActivatedUser
116
+ end # Mixins
117
+ end # Authentication
118
+ end # Merb
@@ -0,0 +1,21 @@
1
+ module Merb
2
+ class Authentication
3
+ module Mixins
4
+ module ActivatedUser
5
+ module ARClassMethods
6
+ def self.extended(base)
7
+ base.class_eval do
8
+ before :create, :make_activation_code
9
+ after :create, :send_signup_notification
10
+ end # base.class_eval
11
+
12
+ def find_with_activation_code(ac)
13
+ find(:first, :conditions => ["activation_code =?", ac])
14
+ end
15
+
16
+ end # self.extended
17
+ end # ARClassMethods
18
+ end # ActivatedUser
19
+ end # Mixins
20
+ end # Authentication
21
+ end # Merb
@@ -0,0 +1,24 @@
1
+ module Merb
2
+ class Authentication
3
+ module Mixins
4
+ module ActivatedUser
5
+ module DMClassMethods
6
+ def self.extended(base)
7
+ base.class_eval do
8
+ property :activated_at, DateTime
9
+ property :activation_code, String
10
+
11
+ before :create, :make_activation_code
12
+ after :create, :send_signup_notification
13
+ end # base.class_eval
14
+
15
+ def find_with_activation_code(ac)
16
+ first(:activation_code => ac)
17
+ end
18
+
19
+ end # self.extended
20
+ end # DMClassMethods
21
+ end # ActivatedUser
22
+ end # Mixins
23
+ end # Authentication
24
+ end #Merb
@@ -0,0 +1,23 @@
1
+ module Merb
2
+ class Authentication
3
+ module Mixins
4
+ module ActivatedUser
5
+ module SQClassMethods
6
+ def self.extended(base)
7
+ base.class_eval do
8
+ before_save :make_activation_code
9
+ after_save :send_signup_notification
10
+ end # base.class_eval
11
+
12
+ def find_with_activation_code(ac)
13
+ self[:activation_code => ac]
14
+ end
15
+ end # self.extended
16
+ end # SQClassMethods
17
+ module SQInstanceMethods
18
+ end # SQInstanceMethods
19
+
20
+ end # ActivatedUser
21
+ end # Mixins
22
+ end # Authentication
23
+ end # Merb
@@ -0,0 +1,18 @@
1
+ namespace :slices do
2
+ namespace :merb_auth_slice_activation do
3
+
4
+ # add your own merb-auth-slice-activation tasks here
5
+
6
+ # implement this to test for structural/code dependencies
7
+ # like certain directories or availability of other files
8
+ desc "Test for any dependencies"
9
+ task :preflight do
10
+ end
11
+
12
+ # implement this to perform any database related setup steps
13
+ desc "Migrate the database"
14
+ task :migrate do
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,75 @@
1
+ namespace :slices do
2
+ namespace :merb_auth_slice_activation do
3
+
4
+ desc "Run slice specs within the host application context"
5
+ task :spec => [ "spec:explain", "spec:default" ]
6
+
7
+ namespace :spec do
8
+
9
+ slice_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
10
+
11
+ task :explain do
12
+ puts "\nNote: By running MerbAuthSliceActivation specs inside the application context any\n" +
13
+ "overrides could break existing specs. This isn't always a problem,\n" +
14
+ "especially in the case of views. Use these spec tasks to check how\n" +
15
+ "well your application conforms to the original slice implementation."
16
+ end
17
+
18
+ Spec::Rake::SpecTask.new('default') do |t|
19
+ t.spec_opts = ["--format", "specdoc", "--colour"]
20
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
21
+ end
22
+
23
+ desc "Run all model specs, run a spec for a specific Model with MODEL=MyModel"
24
+ Spec::Rake::SpecTask.new('model') do |t|
25
+ t.spec_opts = ["--format", "specdoc", "--colour"]
26
+ if(ENV['MODEL'])
27
+ t.spec_files = Dir["#{slice_root}/spec/models/**/#{ENV['MODEL']}_spec.rb"].sort
28
+ else
29
+ t.spec_files = Dir["#{slice_root}/spec/models/**/*_spec.rb"].sort
30
+ end
31
+ end
32
+
33
+ desc "Run all controller specs, run a spec for a specific Controller with CONTROLLER=MyController"
34
+ Spec::Rake::SpecTask.new('controller') do |t|
35
+ t.spec_opts = ["--format", "specdoc", "--colour"]
36
+ if(ENV['CONTROLLER'])
37
+ t.spec_files = Dir["#{slice_root}/spec/controllers/**/#{ENV['CONTROLLER']}_spec.rb"].sort
38
+ else
39
+ t.spec_files = Dir["#{slice_root}/spec/controllers/**/*_spec.rb"].sort
40
+ end
41
+ end
42
+
43
+ desc "Run all view specs, run specs for a specific controller (and view) with CONTROLLER=MyController (VIEW=MyView)"
44
+ Spec::Rake::SpecTask.new('view') do |t|
45
+ t.spec_opts = ["--format", "specdoc", "--colour"]
46
+ if(ENV['CONTROLLER'] and ENV['VIEW'])
47
+ t.spec_files = Dir["#{slice_root}/spec/views/**/#{ENV['CONTROLLER']}/#{ENV['VIEW']}*_spec.rb"].sort
48
+ elsif(ENV['CONTROLLER'])
49
+ t.spec_files = Dir["#{slice_root}/spec/views/**/#{ENV['CONTROLLER']}/*_spec.rb"].sort
50
+ else
51
+ t.spec_files = Dir["#{slice_root}/spec/views/**/*_spec.rb"].sort
52
+ end
53
+ end
54
+
55
+ desc "Run all mailer specs, run a spec for a specific Mailer with MAILER=MyMailer"
56
+ Spec::Rake::SpecTask.new('mailer') do |t|
57
+ t.spec_opts = ["--format", "specdoc", "--colour"]
58
+ if(ENV['MAILER'])
59
+ t.spec_files = Dir["#{slice_root}/spec/mailer/**/#{ENV['MAILER']}_spec.rb"].sort
60
+ else
61
+ t.spec_files = Dir["#{slice_root}/spec/mailers/**/*_spec.rb"].sort
62
+ end
63
+ end
64
+
65
+ desc "Run all specs and output the result in html"
66
+ Spec::Rake::SpecTask.new('html') do |t|
67
+ t.spec_opts = ["--format", "html"]
68
+ t.libs = ['lib', 'server/lib' ]
69
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end