tonkapark-clearance 0.6.9.1 → 0.6.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/CHANGELOG.textile +4 -0
  2. data/README.textile +12 -13
  3. data/Rakefile +3 -3
  4. data/app/controllers/clearance/invitations_controller.rb +28 -0
  5. data/app/controllers/clearance/passwords_controller.rb +1 -1
  6. data/app/controllers/clearance/sessions_controller.rb +1 -1
  7. data/app/controllers/clearance/users_controller.rb +4 -3
  8. data/app/models/clearance_mailer.rb +25 -3
  9. data/app/views/clearance_mailer/invitation.html.erb +3 -0
  10. data/app/views/invitations/index.html.erb +20 -0
  11. data/app/views/invitations/new.html.erb +15 -0
  12. data/app/views/passwords/edit.html.erb +14 -12
  13. data/app/views/passwords/new.html.erb +10 -9
  14. data/app/views/sessions/new.html.erb +1 -26
  15. data/app/views/users/_user.html.erb +28 -0
  16. data/app/views/users/new.html.erb +3 -5
  17. data/clearance.gemspec +12 -3
  18. data/config/clearance_routes.rb +10 -4
  19. data/generators/clearance/clearance_generator.rb +30 -3
  20. data/generators/clearance/templates/README +17 -17
  21. data/generators/clearance/templates/invitation.rb +4 -0
  22. data/generators/clearance/templates/migrations/create_invitations.rb +20 -0
  23. data/generators/clearance/templates/migrations/create_users.rb +2 -0
  24. data/generators/clearance/templates/migrations/update_invitations.rb +40 -0
  25. data/generators/clearance/templates/migrations/update_users.rb +3 -1
  26. data/generators/clearance/templates/user.rb +1 -0
  27. data/lib/clearance/authentication.rb +1 -1
  28. data/lib/clearance/invitation.rb +87 -0
  29. data/lib/clearance/user.rb +26 -2
  30. data/lib/clearance.rb +1 -0
  31. metadata +12 -3
  32. data/app/views/users/_form.html.erb +0 -13
data/CHANGELOG.textile CHANGED
@@ -1,3 +1,7 @@
1
+ h2. 0.6.9.2 (7/21/09)
2
+
3
+ * forked and added invitations and clearance.yml (Matt Anderson[http://tonkapark.com])
4
+
1
5
  h2. 0.6.9 (07/04/2009)
2
6
 
3
7
  * Added timestamps to create users migration. (Dan Croak)
data/README.textile CHANGED
@@ -1,6 +1,6 @@
1
1
  h1. Clearance
2
2
 
3
- Rails authentication with email & password.
3
+ Rails authentication with email & password, *and invitations*.
4
4
 
5
5
  "We have clearance, Clarence.":http://www.youtube.com/v/mNRXJEE3Nz8
6
6
 
@@ -15,10 +15,10 @@ Clearance is a Rails engine. It works with versions of Rails greater than 2.3.
15
15
  In config/environment.rb:
16
16
 
17
17
  <pre>
18
- config.gem "thoughtbot-clearance",
18
+ config.gem "tonkapark-clearance",
19
19
  :lib => 'clearance',
20
20
  :source => 'http://gems.github.com',
21
- :version => '0.6.9'
21
+ :version => '0.6.9.2'
22
22
  </pre>
23
23
 
24
24
  Vendor the gem:
@@ -41,17 +41,16 @@ Run the migration:
41
41
  @rake db:migrate@
42
42
 
43
43
  Define a HOST constant in your environment files.
44
- In config/environments/test.rb and config/environments/development.rb it can be:
45
-
46
- @HOST = "localhost"@
47
-
48
- In production.rb it must be the actual host your application is deployed to.
49
- The constant is used by mailers to generate URLs in emails.
50
-
51
- In config/environment.rb:
52
-
53
- @DO_NOT_REPLY = "donotreply@example.com"@
44
+ In config/clearance.yml:
54
45
 
46
+ <pre>
47
+ production:
48
+ host: you_production_host.com
49
+ do_not_reply: do_not_reply@you_production_host.com
50
+ invitation_subject_line: An Invitation
51
+ invitation_from_email: invitation@you_production_host.com
52
+ </pre>
53
+
55
54
  Define root_url to *something* in your config/routes.rb:
56
55
 
57
56
  @map.root :controller => 'home'@
data/Rakefile CHANGED
@@ -53,11 +53,11 @@ task :default => ['test:all', 'test:features']
53
53
 
54
54
  gem_spec = Gem::Specification.new do |gem_spec|
55
55
  gem_spec.name = "clearance"
56
- gem_spec.version = "0.6.9.1"
57
- gem_spec.summary = "Rails authentication with email & password."
56
+ gem_spec.version = "0.6.9.2"
57
+ gem_spec.summary = "Rails authentication with email & password and invitations."
58
58
  gem_spec.email = "matt@tonkapark.com"
59
59
  gem_spec.homepage = "http://github.com/tonkapark/clearance"
60
- gem_spec.description = "Rails authentication with email & password."
60
+ gem_spec.description = "Rails authentication with email & password and invitations."
61
61
  gem_spec.authors = ["Dan Croak", "Mike Burns", "Jason Morrison",
62
62
  "Joe Ferris", "Eugene Bolshakov", "Nick Quaranto",
63
63
  "Josh Nichols", "Mike Breen", "Marcel Görner",
@@ -0,0 +1,28 @@
1
+ class Clearance::InvitationsController < ApplicationController
2
+ unloadable
3
+
4
+ before_filter :redirect_to_root, :only => [:new, :create], :if => :signed_in?
5
+
6
+ def new
7
+ @invitation = ::Invitation.new(params[:invitation])
8
+ render :template => 'invitations/new'
9
+ end
10
+
11
+ def create
12
+ @invitation = ::Invitation.new(params[:invitation])
13
+ @invitation.sender = current_user
14
+ if @invitation.save
15
+ if signed_in?
16
+ ::ClearanceMailer.deliver_invitation(@invitation, signup_url(@invitation.token))
17
+ flash[:notice] = "Thank you, an invitation has been sent."
18
+ redirect_to root_url
19
+ else
20
+ flash[:notice] = "Thank you for your interest, your invitation has been received."
21
+ redirect_to root_url
22
+ end
23
+ else
24
+ render :action => 'new'
25
+ end
26
+ end
27
+
28
+ end
@@ -68,7 +68,7 @@ class Clearance::PasswordsController < ApplicationController
68
68
  end
69
69
 
70
70
  def url_after_create
71
- new_session_url
71
+ signin_url
72
72
  end
73
73
 
74
74
  def flash_success_after_update
@@ -62,6 +62,6 @@ class Clearance::SessionsController < ApplicationController
62
62
  end
63
63
 
64
64
  def url_after_destroy
65
- new_session_url
65
+ signin_url
66
66
  end
67
67
  end
@@ -4,8 +4,9 @@ class Clearance::UsersController < ApplicationController
4
4
  before_filter :redirect_to_root, :only => [:new, :create], :if => :signed_in?
5
5
  filter_parameter_logging :password
6
6
 
7
- def new
8
- @user = ::User.new(params[:user])
7
+ def new
8
+ @user = ::User.new(:invitation_token => params[:invitation_token])
9
+ @user.email = @user.invitation.recipient_email if @user.invitation
9
10
  render :template => 'users/new'
10
11
  end
11
12
 
@@ -30,6 +31,6 @@ class Clearance::UsersController < ApplicationController
30
31
  end
31
32
 
32
33
  def url_after_create
33
- new_session_url
34
+ signin_url
34
35
  end
35
36
  end
@@ -1,9 +1,22 @@
1
1
  class ClearanceMailer < ActionMailer::Base
2
2
 
3
- default_url_options[:host] = HOST
3
+ def self.config_file
4
+ File.join(RAILS_ROOT, 'config', 'clearance.yml')
5
+ end
6
+
7
+ def self.config
8
+ YAML.load(File.read(config_file)).with_indifferent_access[RAILS_ENV]
9
+ end
10
+
11
+ def config
12
+ self.class.config
13
+ end
14
+
15
+ default_url_options[:host] = config[:host]
16
+
4
17
 
5
18
  def change_password(user)
6
- from DO_NOT_REPLY
19
+ from config[:do_not_reply]
7
20
  recipients user.email
8
21
  subject I18n.t(:change_password,
9
22
  :scope => [:clearance, :models, :clearance_mailer],
@@ -12,12 +25,21 @@ class ClearanceMailer < ActionMailer::Base
12
25
  end
13
26
 
14
27
  def confirmation(user)
15
- from DO_NOT_REPLY
28
+ from config[:do_not_reply]
16
29
  recipients user.email
17
30
  subject I18n.t(:confirmation,
18
31
  :scope => [:clearance, :models, :clearance_mailer],
19
32
  :default => "Account confirmation")
20
33
  body :user => user
21
34
  end
35
+
36
+ def invitation(invitation, url)
37
+ subject config[:invitation_subject_line]
38
+ recipients invitation.recipient_email
39
+ from config[:invitation_from_email]
40
+ body :invitation => invitation, :signup_url => url
41
+ invitation.update_attribute(:invited_at, Time.now)
42
+ end
43
+
22
44
 
23
45
  end
@@ -0,0 +1,3 @@
1
+ You have been invited to join our website.
2
+
3
+ <%= @signup_url %>
@@ -0,0 +1,20 @@
1
+ <h2>Invitations</h2>
2
+
3
+ <table>
4
+ <tr>
5
+ <th>Recipient</th>
6
+ <th>Invited By</th>
7
+ <th>Invited At</th>
8
+ <th>Redeemed At</th>
9
+ <th></th>
10
+ </tr>
11
+
12
+ <% @invitations.each do |invite| %>
13
+ <tr>
14
+ <td><%= invite.recipient_email %></td>
15
+ <td><%= invite.invited_at %></td>
16
+ <td><%= invite.redeemed_at %></td>
17
+ <td><%= link_to "Redeem Link", signup_url(invite.token) %></td>
18
+ </tr>
19
+ <% end %>
20
+ </table>
@@ -0,0 +1,15 @@
1
+ <h2>Request Invitation</h2>
2
+
3
+ <% form_for @invitation do |f| %>
4
+ <%= f.error_messages %>
5
+ <ul>
6
+ <li>
7
+ <%= f.label :recipient_email, "Your Email" %><br />
8
+ <p class="instructions">This email will be used for your account.</p>
9
+ <%= f.text_field :recipient_email, {:class => "big"} %>
10
+ </li>
11
+ </ul>
12
+ <p><%= f.submit "Submit" %></p>
13
+ <% end %>
14
+
15
+
@@ -9,15 +9,17 @@
9
9
  <% form_for(:user,
10
10
  :url => user_password_path(@user, :token => @user.token),
11
11
  :html => { :method => :put }) do |form| %>
12
- <div class="password_field">
13
- <%= form.label :password, "Choose password" %>
14
- <%= form.password_field :password %>
15
- </div>
16
- <div class="password_field">
17
- <%= form.label :password_confirmation, "Confirm password" %>
18
- <%= form.password_field :password_confirmation %>
19
- </div>
20
- <div class="submit_field">
21
- <%= form.submit "Save this password", :disable_with => "Please wait..." %>
22
- </div>
23
- <% end %>
12
+ <ul>
13
+ <li>
14
+ <%= form.label :password, "Choose password" %>
15
+ <%= form.password_field :password, {:class => "big"} %>
16
+ </li>
17
+ <li>
18
+ <%= form.label :password_confirmation, "Confirm password" %>
19
+ <%= form.password_field :password_confirmation, {:class => "big"} %>
20
+ </li>
21
+ <li class="submit">
22
+ <%= form.submit "Save this password", :disable_with => "Please wait..." %>
23
+ </li>
24
+ </ul>
25
+ <% end %>
@@ -1,15 +1,16 @@
1
1
  <h2>Change your password</h2>
2
-
3
2
  <p>
4
3
  We will email you a link to change your password.
5
4
  </p>
6
5
 
7
6
  <% form_for :password, :url => passwords_path do |form| %>
8
- <div class="text_field">
9
- <%= form.label :email, "Email address" %>
10
- <%= form.text_field :email %>
11
- </div>
12
- <div class="submit_field">
13
- <%= form.submit "Reset password", :disable_with => "Please wait..." %>
14
- </div>
15
- <% end %>
7
+ <ul>
8
+ <li>
9
+ <%= form.label :email, "Email address" %>
10
+ <%= form.text_field :email, {:class => "big"} %>
11
+ </li>
12
+ <li class="submit">
13
+ <%= form.submit "Reset password", :disable_with => "Please wait..." %>
14
+ </li>
15
+ </ul>
16
+ <% end %>
@@ -1,28 +1,3 @@
1
1
  <h2>Sign in</h2>
2
2
 
3
- <% form_for :session, :url => session_path do |form| %>
4
- <div class="text_field">
5
- <%= form.label :email %>
6
- <%= form.text_field :email %>
7
- </div>
8
- <div class="text_field">
9
- <%= form.label :password %>
10
- <%= form.password_field :password %>
11
- </div>
12
- <div class="text_field">
13
- <%= form.check_box :remember_me %>
14
- <%= form.label :remember_me %>
15
- </div>
16
- <div class="submit_field">
17
- <%= form.submit "Sign in", :disable_with => "Please wait..." %>
18
- </div>
19
- <% end %>
20
-
21
- <ul>
22
- <li>
23
- <%= link_to "Sign up", new_user_path %>
24
- </li>
25
- <li>
26
- <%= link_to "Forgot password?", new_password_path %>
27
- </li>
28
- </ul>
3
+ <%= render :partial => 'sessions/form' %>
@@ -0,0 +1,28 @@
1
+
2
+ <% form_for(@user, :html => {:id => "user_form"}) do |form| %>
3
+
4
+ <%= form.error_messages %>
5
+
6
+ <ul>
7
+ <li>
8
+ <label for="user_email">Email <small>We'll keep it a secret.</small></label>
9
+ <%= form.text_field :email, {:class => "big"} %>
10
+ </li>
11
+
12
+ <li>
13
+ <%= form.label :password %>
14
+ <%= form.password_field :password, {:class => "big"} %>
15
+ </li>
16
+
17
+ <li>
18
+ <%= form.label :password_confirmation, "Confirm password" %>
19
+ <%= form.password_field :password_confirmation, {:class => "big"} %>
20
+ </li>
21
+
22
+ <% if @user.new_record? %>
23
+ <%= form.hidden_field :invitation_token %>
24
+ <% end %>
25
+
26
+ <li class="submit"><%= form.submit button_name, :disable_with => 'Please wait...' %> | <% if @user.new_record? %><%= link_to 'Cancel', root_url %><% else %><%= link_to 'Sign In', signin_path %><% end %></li>
27
+ </ul>
28
+ <% end %>
@@ -1,6 +1,4 @@
1
- <h2>Sign up</h2>
1
+ <h2>Signup</h2>
2
+
3
+ <%= render :partial => 'users/user', :locals => {:button_name => "Create my account"} %>
2
4
 
3
- <% form_for @user do |form| %>
4
- <%= render :partial => '/users/form', :object => form %>
5
- <%= form.submit 'Sign up', :disable_with => 'Please wait...' %>
6
- <% end %>
data/clearance.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clearance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.9.1
4
+ version: 0.6.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Croak
@@ -28,7 +28,7 @@ date: 2009-07-16 00:00:00 -05:00
28
28
  default_executable:
29
29
  dependencies: []
30
30
 
31
- description: Rails authentication with email & password.
31
+ description: Rails authentication with email & password and invitations.
32
32
  email: matt@tonkapark.com
33
33
  executables: []
34
34
 
@@ -53,6 +53,7 @@ files:
53
53
  - app/controllers
54
54
  - app/controllers/clearance
55
55
  - app/controllers/clearance/confirmations_controller.rb
56
+ - app/controllers/clearance/invitations_controller.rb
56
57
  - app/controllers/clearance/passwords_controller.rb
57
58
  - app/controllers/clearance/sessions_controller.rb
58
59
  - app/controllers/clearance/users_controller.rb
@@ -62,6 +63,7 @@ files:
62
63
  - app/views/clearance_mailer
63
64
  - app/views/clearance_mailer/change_password.html.erb
64
65
  - app/views/clearance_mailer/confirmation.html.erb
66
+ - app/views/clearance_mailer/invitation.html.erb
65
67
  - app/views/passwords
66
68
  - app/views/passwords/edit.html.erb
67
69
  - app/views/passwords/new.html.erb
@@ -69,7 +71,10 @@ files:
69
71
  - app/views/sessions/new.html.erb
70
72
  - app/views/users
71
73
  - app/views/users/new.html.erb
72
- - app/views/users/_form.html.erb
74
+ - app/views/users/_user.html.erb
75
+ - app/views/invitations
76
+ - app/views/invitations/new.html.erb
77
+ - app/views/invitations/index.html.erb
73
78
  - config/clearance_routes.rb
74
79
  - generators/clearance
75
80
  - generators/clearance/clearance_generator.rb
@@ -81,8 +86,11 @@ files:
81
86
  - generators/clearance/templates/migrations
82
87
  - generators/clearance/templates/migrations/create_users.rb
83
88
  - generators/clearance/templates/migrations/update_users.rb
89
+ - generators/clearance/templates/migrations/create_invitations.rb
90
+ - generators/clearance/templates/migrations/update_invitations.rb
84
91
  - generators/clearance/templates/README
85
92
  - generators/clearance/templates/user.rb
93
+ - generators/clearance/templates/invitation.rb
86
94
  - generators/clearance/USAGE
87
95
  - generators/clearance_features
88
96
  - generators/clearance_features/clearance_features_generator.rb
@@ -104,6 +112,7 @@ files:
104
112
  - lib/clearance/extensions/errors.rb
105
113
  - lib/clearance/extensions/rescue.rb
106
114
  - lib/clearance/extensions/routes.rb
115
+ - lib/clearance/invitation.rb
107
116
  - lib/clearance/user.rb
108
117
  - lib/clearance.rb
109
118
  - shoulda_macros/clearance.rb
@@ -1,7 +1,7 @@
1
1
  ActionController::Routing::Routes.draw do |map|
2
2
  map.resources :passwords,
3
3
  :controller => 'clearance/passwords',
4
- :only => [:new, :create]
4
+ :only => [:create]
5
5
 
6
6
  map.resource :session,
7
7
  :controller => 'clearance/sessions',
@@ -17,13 +17,19 @@ ActionController::Routing::Routes.draw do |map|
17
17
  :only => [:new, :create]
18
18
  end
19
19
 
20
- map.sign_up 'sign_up',
20
+ map.resources :invitations,
21
+ :controller => 'clearance/invitations',
22
+ :only => [:new, :create]
23
+
24
+ map.recover_password '/recover_password', :controller =>'clearance/passwords', :action => 'new'
25
+
26
+ map.signup '/signup/:invitation_token',
21
27
  :controller => 'clearance/users',
22
28
  :action => 'new'
23
- map.sign_in 'sign_in',
29
+ map.signin 'login',
24
30
  :controller => 'clearance/sessions',
25
31
  :action => 'new'
26
- map.sign_out 'sign_out',
32
+ map.logout 'logout',
27
33
  :controller => 'clearance/sessions',
28
34
  :action => 'destroy',
29
35
  :method => :delete
@@ -12,6 +12,7 @@ class ClearanceGenerator < Rails::Generator::Base
12
12
  user_model = "app/models/user.rb"
13
13
  if File.exists?(user_model)
14
14
  m.insert_into user_model, "include Clearance::User"
15
+ m.insert_into user_model, "belongs_to :invitation"
15
16
  else
16
17
  m.directory File.join("app", "models")
17
18
  m.file "user.rb", user_model
@@ -20,17 +21,35 @@ class ClearanceGenerator < Rails::Generator::Base
20
21
  m.directory File.join("test", "factories")
21
22
  m.file "factories.rb", "test/factories/clearance.rb"
22
23
 
23
- m.migration_template "migrations/#{migration_name}.rb",
24
+ m.migration_template "migrations/#{user_migration_name}.rb",
24
25
  'db/migrate',
25
- :migration_file_name => "clearance_#{migration_name}"
26
+ :migration_file_name => "clearance_#{user_migration_name}"
26
27
 
28
+ m.directory File.join("config")
29
+ m.file "clearance.yml", 'config/clearance.yml'
30
+
31
+ invitation_model = "app/models/invitation.rb"
32
+ if File.exists?(invitation_model)
33
+ m.insert_into invitation_model, "include Clearance::Invitation"
34
+ m.insert_into invitation_model, "belongs_to :sender, :class_name => 'User', :foreign_key => :sender_id"
35
+ else
36
+ m.directory File.join("app", "models")
37
+ m.file "invitation.rb", invitation_model
38
+ end
39
+
40
+ m.migration_template "migrations/#{invitation_migration_name}.rb",
41
+ 'db/migrate',
42
+ :migration_file_name => "clearance_#{invitation_migration_name}"
43
+
44
+
45
+
27
46
  m.readme "README"
28
47
  end
29
48
  end
30
49
 
31
50
  private
32
51
 
33
- def migration_name
52
+ def user_migration_name
34
53
  if ActiveRecord::Base.connection.table_exists?(:users)
35
54
  'update_users'
36
55
  else
@@ -38,4 +57,12 @@ class ClearanceGenerator < Rails::Generator::Base
38
57
  end
39
58
  end
40
59
 
60
+ def invitation_migration_name
61
+ if ActiveRecord::Base.connection.table_exists?(:invitations)
62
+ 'update_invitations'
63
+ else
64
+ 'create_invitations'
65
+ end
66
+ end
67
+
41
68
  end
@@ -1,22 +1,22 @@
1
1
 
2
2
  *******************************************************************************
3
-
3
+
4
4
  Ok, enough fancy automatic stuff. Time for some old school monkey copy-pasting.
5
-
6
- 1. Define a HOST constant in your environments files.
7
- In config/environments/test.rb and config/environments/development.rb it can be:
8
-
9
- HOST = "localhost"
10
-
11
- In production.rb it must be the actual host your application is deployed to.
12
- The constant is used by mailers to generate URLs in emails.
13
-
14
- 2. In config/environment.rb:
15
-
16
- DO_NOT_REPLY = "donotreply@example.com"
17
-
18
- 3. Define root_url to *something* in your config/routes.rb:
19
-
5
+
6
+ 1. A a clearance.yml config file has been created in your config/ directory.
7
+
8
+ Use it to configure the host name and sender of clearance generated emails.
9
+ In production it must be the actual host your application is deployed to.
10
+ The config is used by mailers to generate URLs in emails.
11
+
12
+ It can look like this:
13
+
14
+ production:
15
+ host: you_production_host.com
16
+ do_not_reply: do_not_reply@you_production_host.com
17
+
18
+ 2. Define root_url to *something* in your config/routes.rb:
19
+
20
20
  map.root :controller => 'home'
21
-
21
+
22
22
  *******************************************************************************
@@ -0,0 +1,4 @@
1
+ class Invitation < ActiveRecord::Base
2
+ include Clearance::Invitation
3
+ belongs_to :sender, :class_name => 'User', :foreign_key => :sender_id
4
+ end
@@ -0,0 +1,20 @@
1
+ class ClearanceCreateInvitations < ActiveRecord::Migration
2
+ def self.up
3
+ create_table(:invitations) do |t|
4
+ t.integer :sender_id
5
+ t.string :recipient_email
6
+ t.string :token
7
+ t.datetime :invited_at
8
+ t.datetime :redeemed_at
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :invitations, [:id, :token]
13
+ add_index :invitations, :recipient_email
14
+ add_index :invitations, :token
15
+ end
16
+
17
+ def self.down
18
+ drop_table :invitations
19
+ end
20
+ end
@@ -7,6 +7,8 @@ class ClearanceCreateUsers < ActiveRecord::Migration
7
7
  t.string :token, :limit => 128
8
8
  t.datetime :token_expires_at
9
9
  t.boolean :email_confirmed, :default => false, :null => false
10
+ t.integer :invitation_id
11
+ t.integer :invitation_limit
10
12
  t.timestamps
11
13
  end
12
14
 
@@ -0,0 +1,40 @@
1
+ class ClearanceUpdateUsers < ActiveRecord::Migration
2
+ def self.up
3
+ <%
4
+ existing_columns = ActiveRecord::Base.connection.columns(:users).collect { |each| each.name }
5
+ columns = [
6
+ [:sender_id, 't.integer :sender_id'],
7
+ [:recipient_email, 't.string :recipient_email, :limit => 128'],
8
+ [:token, 't.string :token, :limit => 128'],
9
+ [:invited_at, 't.datetime :invited_at'],
10
+ [:redeemed_at, 't.datetime :redeemed_at']
11
+ ].delete_if {|c| existing_columns.include?(c.first.to_s)}
12
+ -%>
13
+ change_table(:users) do |t|
14
+ <% columns.each do |c| -%>
15
+ <%= c.last %>
16
+ <% end -%>
17
+ end
18
+
19
+ <%
20
+ existing_indexes = ActiveRecord::Base.connection.indexes(:users)
21
+ index_names = existing_indexes.collect { |each| each.name }
22
+ new_indexes = [
23
+ [:index_invitations_on_id_and_token, 'add_index :invitations, [:id, :token]'],
24
+ [:index_invitations_on_email, 'add_index :invitations, :recipient_email'],
25
+ [:index_invitations_on_token, 'add_index :invitations, :token']
26
+ ].delete_if { |each| index_names.include?(each.first.to_s) }
27
+ -%>
28
+ <% new_indexes.each do |each| -%>
29
+ <%= each.last %>
30
+ <% end -%>
31
+ end
32
+
33
+ def self.down
34
+ change_table(:invitations) do |t|
35
+ <% unless columns.empty? -%>
36
+ t.remove <%= columns.collect { |each| ":#{each.first}" }.join(',') %>
37
+ <% end -%>
38
+ end
39
+ end
40
+ end
@@ -8,7 +8,9 @@ class ClearanceUpdateUsers < ActiveRecord::Migration
8
8
  [:salt, 't.string :salt, :limit => 128'],
9
9
  [:token, 't.string :token, :limit => 128'],
10
10
  [:token_expires_at, 't.datetime :token_expires_at'],
11
- [:email_confirmed, 't.boolean :email_confirmed, :default => false, :null => false']
11
+ [:email_confirmed, 't.boolean :email_confirmed, :default => false, :null => false'],
12
+ [:invitation_id, 't.integer :invitation_id'],
13
+ [:invitation_limit, 't.integer :invitation_limit']
12
14
  ].delete_if {|c| existing_columns.include?(c.first.to_s)}
13
15
  -%>
14
16
  change_table(:users) do |t|
@@ -1,3 +1,4 @@
1
1
  class User < ActiveRecord::Base
2
2
  include Clearance::User
3
+ belongs_to :invitation
3
4
  end
@@ -92,7 +92,7 @@ module Clearance
92
92
  def deny_access(flash_message = nil, opts = {})
93
93
  store_location
94
94
  flash[:failure] = flash_message if flash_message
95
- redirect_to(new_session_url)
95
+ redirect_to(signin_url)
96
96
  end
97
97
  end
98
98
 
@@ -0,0 +1,87 @@
1
+ module Clearance
2
+ module Invitation
3
+
4
+ def self.included(model)
5
+ model.extend(ClassMethods)
6
+
7
+ model.send(:include, InstanceMethods)
8
+ model.send(:include, AttrAccessible)
9
+ model.send(:include, AttrAccessor)
10
+ model.send(:include, Validations)
11
+ model.send(:include, Callbacks)
12
+ end
13
+
14
+ module AttrAccessible
15
+ def self.included(model)
16
+ model.class_eval do
17
+ attr_accessible :recipient_email
18
+ end
19
+ end
20
+ end
21
+
22
+ module AttrAccessor
23
+ def self.included(model)
24
+ model.class_eval do
25
+
26
+ end
27
+ end
28
+ end
29
+
30
+ module Validations
31
+ def self.included(model)
32
+ model.class_eval do
33
+ validates_presence_of :recipient_email
34
+ validates_format_of :recipient_email, :with => %r{.+@.+\..+}
35
+
36
+ validate :recipient_is_not_registered
37
+ validate :sender_has_invitations, :if => :sender
38
+ end
39
+ end
40
+ end
41
+
42
+ module Callbacks
43
+ def self.included(model)
44
+ model.class_eval do
45
+ before_create :generate_token
46
+ before_create :decrement_sender_count, :if => :sender
47
+ end
48
+ end
49
+ end
50
+
51
+ module InstanceMethods
52
+
53
+ def redeemed!
54
+ self.redeemed_at = Time.now.utc
55
+ self.save!
56
+ end
57
+
58
+ def self.find_redeemable(invitation_code)
59
+ self.find(:first, :conditions => {:redeemed_at => nil, :token => invitation_code})
60
+ end
61
+
62
+ protected
63
+
64
+ def recipient_is_not_registered
65
+ errors.add :recipient_email, 'is already registered' if ::User.find_by_email(recipient_email)
66
+ end
67
+
68
+ def sender_has_invitations
69
+ unless sender.invitation_limit > 0
70
+ errors.add_to_base 'You have reached your limit of invitations to send.'
71
+ end
72
+ end
73
+
74
+ def generate_token
75
+ self.token = Digest::SHA1.hexdigest([Time.now, rand].join)
76
+ end
77
+
78
+ def decrement_sender_count
79
+ sender.decrement! :invitation_limit
80
+ end
81
+ end
82
+
83
+ module ClassMethods
84
+ end
85
+
86
+ end
87
+ end
@@ -16,7 +16,7 @@ module Clearance
16
16
  module AttrAccessible
17
17
  def self.included(model)
18
18
  model.class_eval do
19
- attr_accessible :email, :password, :password_confirmation
19
+ attr_accessible :email, :password, :password_confirmation, :invitation_token
20
20
  end
21
21
  end
22
22
  end
@@ -38,6 +38,9 @@ module Clearance
38
38
 
39
39
  validates_presence_of :password, :if => :password_required?
40
40
  validates_confirmation_of :password, :if => :password_required?
41
+
42
+ validates_presence_of :invitation_id, :message => 'is required', :if => Proc.new { |u| u.new_record?}
43
+ validates_uniqueness_of :invitation_id, :if => Proc.new { |u| u.new_record?}
41
44
  end
42
45
  end
43
46
  end
@@ -45,7 +48,8 @@ module Clearance
45
48
  module Callbacks
46
49
  def self.included(model)
47
50
  model.class_eval do
48
- before_save :initialize_salt, :encrypt_password, :initialize_token
51
+ before_save :initialize_salt, :encrypt_password, :initialize_token, :downcase_email
52
+ before_create :set_invitation_limit, :redeemed
49
53
  end
50
54
  end
51
55
  end
@@ -89,6 +93,14 @@ module Clearance
89
93
  clear_token if valid?
90
94
  save
91
95
  end
96
+
97
+ def invitation_token
98
+ invitation.token if invitation
99
+ end
100
+
101
+ def invitation_token=(token)
102
+ self.invitation = ::Invitation.find_by_token(token)
103
+ end
92
104
 
93
105
  protected
94
106
 
@@ -130,6 +142,18 @@ module Clearance
130
142
  self.token = encrypt("--#{token_expires_at}--#{password}--")
131
143
  save(false)
132
144
  end
145
+
146
+ def downcase_email
147
+ self.email = email.to_s.downcase
148
+ end
149
+
150
+ def set_invitation_limit
151
+ self.invitation_limit = 5
152
+ end
153
+
154
+ def redeemed
155
+ invitation.redeemed! if invitation
156
+ end
133
157
  end
134
158
 
135
159
  module ClassMethods
data/lib/clearance.rb CHANGED
@@ -4,3 +4,4 @@ require 'clearance/extensions/routes'
4
4
 
5
5
  require 'clearance/authentication'
6
6
  require 'clearance/user'
7
+ require 'clearance/invitation'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tonkapark-clearance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.9.1
4
+ version: 0.6.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Croak
@@ -28,7 +28,7 @@ date: 2009-07-15 22:00:00 -07:00
28
28
  default_executable:
29
29
  dependencies: []
30
30
 
31
- description: Rails authentication with email & password.
31
+ description: Rails authentication with email & password and invitations.
32
32
  email: matt@tonkapark.com
33
33
  executables: []
34
34
 
@@ -53,6 +53,7 @@ files:
53
53
  - app/controllers
54
54
  - app/controllers/clearance
55
55
  - app/controllers/clearance/confirmations_controller.rb
56
+ - app/controllers/clearance/invitations_controller.rb
56
57
  - app/controllers/clearance/passwords_controller.rb
57
58
  - app/controllers/clearance/sessions_controller.rb
58
59
  - app/controllers/clearance/users_controller.rb
@@ -62,6 +63,7 @@ files:
62
63
  - app/views/clearance_mailer
63
64
  - app/views/clearance_mailer/change_password.html.erb
64
65
  - app/views/clearance_mailer/confirmation.html.erb
66
+ - app/views/clearance_mailer/invitation.html.erb
65
67
  - app/views/passwords
66
68
  - app/views/passwords/edit.html.erb
67
69
  - app/views/passwords/new.html.erb
@@ -69,7 +71,10 @@ files:
69
71
  - app/views/sessions/new.html.erb
70
72
  - app/views/users
71
73
  - app/views/users/new.html.erb
72
- - app/views/users/_form.html.erb
74
+ - app/views/users/_user.html.erb
75
+ - app/views/invitations
76
+ - app/views/invitations/new.html.erb
77
+ - app/views/invitations/index.html.erb
73
78
  - config/clearance_routes.rb
74
79
  - generators/clearance
75
80
  - generators/clearance/clearance_generator.rb
@@ -81,8 +86,11 @@ files:
81
86
  - generators/clearance/templates/migrations
82
87
  - generators/clearance/templates/migrations/create_users.rb
83
88
  - generators/clearance/templates/migrations/update_users.rb
89
+ - generators/clearance/templates/migrations/create_invitations.rb
90
+ - generators/clearance/templates/migrations/update_invitations.rb
84
91
  - generators/clearance/templates/README
85
92
  - generators/clearance/templates/user.rb
93
+ - generators/clearance/templates/invitation.rb
86
94
  - generators/clearance/USAGE
87
95
  - generators/clearance_features
88
96
  - generators/clearance_features/clearance_features_generator.rb
@@ -104,6 +112,7 @@ files:
104
112
  - lib/clearance/extensions/errors.rb
105
113
  - lib/clearance/extensions/rescue.rb
106
114
  - lib/clearance/extensions/routes.rb
115
+ - lib/clearance/invitation.rb
107
116
  - lib/clearance/user.rb
108
117
  - lib/clearance.rb
109
118
  - shoulda_macros/clearance.rb
@@ -1,13 +0,0 @@
1
- <%= form.error_messages %>
2
- <div class="text_field">
3
- <%= form.label :email %>
4
- <%= form.text_field :email %>
5
- </div>
6
- <div class="password_field">
7
- <%= form.label :password %>
8
- <%= form.password_field :password %>
9
- </div>
10
- <div class="password_field">
11
- <%= form.label :password_confirmation, "Confirm password" %>
12
- <%= form.password_field :password_confirmation %>
13
- </div>