invitation 0.0.1
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.
- checksums.yaml +7 -0
- data/Rakefile +6 -0
- data/app/controllers/invitation/invites_controller.rb +66 -0
- data/app/mailers/invite_mailer.rb +20 -0
- data/app/models/invite.rb +28 -0
- data/app/views/invite_mailer/existing_user.html.erb +7 -0
- data/app/views/invite_mailer/new_user.html.erb +9 -0
- data/app/views/invites/new.html.erb +17 -0
- data/config/locales/invitation.en.yml +21 -0
- data/config/routes.rb +5 -0
- data/lib/generators/invitation/controllers/USAGE +10 -0
- data/lib/generators/invitation/controllers/controllers_generator.rb +21 -0
- data/lib/generators/invitation/helpers.rb +78 -0
- data/lib/generators/invitation/install/USAGE +8 -0
- data/lib/generators/invitation/install/install_generator.rb +77 -0
- data/lib/generators/invitation/install/templates/db/migrate/create_invites.rb +18 -0
- data/lib/generators/invitation/install/templates/invitation.rb +7 -0
- data/lib/generators/invitation/routes/routes_generator.rb +35 -0
- data/lib/generators/invitation/routes/templates/routes.rb +1 -0
- data/lib/generators/invitation/views/USAGE +11 -0
- data/lib/generators/invitation/views/views_generator.rb +21 -0
- data/lib/invitation/configuration.rb +90 -0
- data/lib/invitation/engine.rb +10 -0
- data/lib/invitation/invitable.rb +64 -0
- data/lib/invitation/user.rb +38 -0
- data/lib/invitation/user_registration.rb +52 -0
- data/lib/invitation/version.rb +3 -0
- data/lib/invitation.rb +11 -0
- data/lib/tasks/invitation_tasks.rake +4 -0
- metadata +218 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e0724862de922a3160c5377adb5b82c528338d99
|
4
|
+
data.tar.gz: 4da9685c29934fac4f2032745cd82f26bfc4e659
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 00cafe245f0461abbed65b703385fd947966f34ff3c5951ae5ec400b4afefae8c72a3a10094a76c7cc2e41b28efc2777e0825093e3db244fba460be42fe49a9b
|
7
|
+
data.tar.gz: 7d4cf63f017e0df43630c5eced0746534f7805a4f68a8dc67d76078129c69f61514782b2ff21ca01a53457cba6e26c2182b70b0a68e34164979e5c58b957b958
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
class Invitation::InvitesController < ApplicationController
|
2
|
+
|
3
|
+
def new
|
4
|
+
@invite = invite_from_params
|
5
|
+
render template: 'invites/new'
|
6
|
+
end
|
7
|
+
|
8
|
+
def create
|
9
|
+
@invite = invite_from_params
|
10
|
+
@invite.sender_id = current_user.id
|
11
|
+
logger.info '@invite: ' + @invite.inspect
|
12
|
+
if @invite.save
|
13
|
+
#if the user already exists
|
14
|
+
if @invite.recipient != nil
|
15
|
+
deliver_email(InviteMailer.existing_user(@invite))
|
16
|
+
after_invite_existing_user
|
17
|
+
else
|
18
|
+
deliver_email(InviteMailer.new_user(@invite))
|
19
|
+
after_invite_new_user
|
20
|
+
end
|
21
|
+
flash[:notice] = t('invitation.flash.invite_issued', email: @invite.email)
|
22
|
+
else
|
23
|
+
flash[:error] = t('invitation.flash.invite_error')
|
24
|
+
end
|
25
|
+
redirect_to url_after_invite
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
|
32
|
+
# Override this if you want to do something more complicated for existing users.
|
33
|
+
def after_invite_existing_user
|
34
|
+
# Add the user to the organization
|
35
|
+
@invite.invitable.add_invited_user(@invite.recipient)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Override if you want to do something more complicated for new users. By default we do nothing.
|
39
|
+
def after_invite_new_user
|
40
|
+
end
|
41
|
+
|
42
|
+
# Url sender is redirected to after creating invite.
|
43
|
+
def url_after_invite
|
44
|
+
Invitation.configuration.url_after_invite
|
45
|
+
end
|
46
|
+
|
47
|
+
# Build new Invite from params.
|
48
|
+
def invite_from_params
|
49
|
+
Invite.new(invite_params)
|
50
|
+
end
|
51
|
+
|
52
|
+
def invite_params
|
53
|
+
return params.require(:invite).permit(:invitable_id, :invitable_type, :email) if params[:invite]
|
54
|
+
Hash.new
|
55
|
+
end
|
56
|
+
|
57
|
+
# Use deliver_later from rails 4.2+ if available.
|
58
|
+
def deliver_email(mail)
|
59
|
+
if mail.respond_to?(:deliver_later)
|
60
|
+
mail.deliver_later
|
61
|
+
else
|
62
|
+
mail.deliver
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class InviteMailer < ActionMailer::Base
|
2
|
+
def existing_user(invite)
|
3
|
+
@invite = invite
|
4
|
+
mail(
|
5
|
+
from: Invitation.configuration.mailer_sender,
|
6
|
+
to: @invite.email,
|
7
|
+
subject: I18n.t('invitation.invite_mailer.existing_user.subject')
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def new_user(invite)
|
12
|
+
@invite = invite
|
13
|
+
@user_registration_url = Invitation.configuration.user_registration_url.call(:invite_token => @invite.token)
|
14
|
+
mail(
|
15
|
+
from: Invitation.configuration.mailer_sender,
|
16
|
+
to: @invite.email,
|
17
|
+
subject: I18n.t('invitation.invite_mailer.new_user.subject')
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# An invitation, tracks the sender and recipient, and what the recipient is invited to.
|
2
|
+
# Generates a unique token for each invitation. The token is used in the invite url
|
3
|
+
# to (more) securely identify the invite when a new user clicks to register.
|
4
|
+
#
|
5
|
+
class Invite < ActiveRecord::Base
|
6
|
+
belongs_to :invitable, polymorphic: true
|
7
|
+
belongs_to :sender, class_name: Invitation.configuration.user_model_class_name
|
8
|
+
belongs_to :recipient, class_name: Invitation.configuration.user_model_class_name
|
9
|
+
|
10
|
+
before_create :generate_token
|
11
|
+
before_save :check_recipient_existence
|
12
|
+
|
13
|
+
validates :email, presence: true
|
14
|
+
validates :invitable, presence: true
|
15
|
+
validates :sender, presence: true
|
16
|
+
|
17
|
+
|
18
|
+
def generate_token
|
19
|
+
self.token = SecureRandom.hex(20).encode('UTF-8')
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_recipient_existence
|
23
|
+
recipient = Invitation.configuration.user_model.find_by_email(email)
|
24
|
+
if recipient
|
25
|
+
self.recipient_id = recipient.id
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<p><%= t('invitation.invite_mailer.existing_user.hello', email: @invite.email) %></p>
|
2
|
+
|
3
|
+
<p><%= raw t('invitation.invite_mailer.existing_user.someone_invited_you',
|
4
|
+
sender: @invite.sender.email, invitable: @invite.invitable.invitable_name, url: root_url) %></p>
|
5
|
+
|
6
|
+
<p><%= t('invitation.invite_mailer.existing_user.ignore', sender: @invite.sender.email) %></p>
|
7
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<p><%= t('invitation.invite_mailer.new_user.hello', email: @invite.email) %></p>
|
2
|
+
|
3
|
+
<p><%= raw t('invitation.invite_mailer.new_user.someone_invited_you',
|
4
|
+
sender: @invite.sender.email, invitable: @invite.invitable.invitable_name, url: root_url) %></p>
|
5
|
+
|
6
|
+
<p><%= link_to t('invitation.invite_mailer.new_user.accept'), @user_registration_url %></p>
|
7
|
+
|
8
|
+
<p><%= t('invitation.invite_mailer.new_user.ignore').html_safe %></p>
|
9
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<div id="invitation" class="new">
|
2
|
+
|
3
|
+
|
4
|
+
<p>
|
5
|
+
Issue an invitation to join <%= @invite.invitable.invitable_name %>
|
6
|
+
</p>
|
7
|
+
|
8
|
+
<%= form_for @invite, url: invites_path do |f| %>
|
9
|
+
<%= f.hidden_field :invitable_id, value: @invite.invitable_id %>
|
10
|
+
<%= f.hidden_field :invitable_type, value: @invite.invitable_type %>
|
11
|
+
<%= f.label :email %>
|
12
|
+
<%= f.email_field :email %>
|
13
|
+
<%= f.submit 'Send' %>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
</div>
|
17
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
en:
|
2
|
+
activerecord:
|
3
|
+
attributes:
|
4
|
+
invite:
|
5
|
+
email: "Email"
|
6
|
+
invitation:
|
7
|
+
flash:
|
8
|
+
invite_issued: "Invitation issued to %{email}"
|
9
|
+
invite_error: "Unable to issue invitation"
|
10
|
+
invite_mailer:
|
11
|
+
existing_user:
|
12
|
+
subject: "Invitation instructions"
|
13
|
+
hello: "Hello %{email}"
|
14
|
+
someone_invited_you: "%{sender} has invited you to <b>%{invitable}</b> at %{url}. You now have permissions to access it."
|
15
|
+
ignore: "If you don't want this permission added or feel it is in error, please contact %{sender}."
|
16
|
+
new_user:
|
17
|
+
subject: "Invitation instructions"
|
18
|
+
hello: "Hello %{email}"
|
19
|
+
someone_invited_you: "%{sender} has invited you to <b>%{invitable}</b> at %{url}. You can accept the invitation with the url below."
|
20
|
+
accept: "Accept invitation"
|
21
|
+
ignore: "If you don't want to accept the invitation, please ignore this email.<br />Your account won't be created until you access the link above and set your password."
|
data/config/routes.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Description:
|
2
|
+
Override the default invitation controller and mailer. This generator will copy all of the
|
3
|
+
invitation controllers and mailers into your project.
|
4
|
+
|
5
|
+
Examples:
|
6
|
+
rails generate invitation:controllers
|
7
|
+
|
8
|
+
Controller: app/controllers/invitation/invites_controller.rb
|
9
|
+
Mailer: app/mailers/invitation_mailer.rb
|
10
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
|
3
|
+
#
|
4
|
+
# deploy view and locale assets
|
5
|
+
#
|
6
|
+
module Invitation
|
7
|
+
module Generators
|
8
|
+
class ControllersGenerator < Rails::Generators::Base
|
9
|
+
source_root File.expand_path("../../../../..", __FILE__)
|
10
|
+
|
11
|
+
def create_controllers
|
12
|
+
directory 'app/controllers'
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_mailers
|
16
|
+
directory 'app/mailers'
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Invitation
|
2
|
+
module Generators
|
3
|
+
module Helpers
|
4
|
+
private
|
5
|
+
|
6
|
+
# Either return the model passed in a classified form or return the default "User".
|
7
|
+
def model_class_name
|
8
|
+
options[:model] ? options[:model].classify : 'User'
|
9
|
+
end
|
10
|
+
|
11
|
+
def model_path
|
12
|
+
@model_path ||= File.join('app', 'models', "#{file_path}.rb")
|
13
|
+
end
|
14
|
+
|
15
|
+
def file_path
|
16
|
+
model_name.underscore
|
17
|
+
end
|
18
|
+
|
19
|
+
def namespace
|
20
|
+
Rails::Generators.namespace if Rails::Generators.respond_to?(:namespace)
|
21
|
+
end
|
22
|
+
|
23
|
+
def namespaced?
|
24
|
+
!!namespace
|
25
|
+
end
|
26
|
+
|
27
|
+
def model_name
|
28
|
+
if namespaced?
|
29
|
+
[namespace.to_s] + [model_class_name]
|
30
|
+
else
|
31
|
+
[model_class_name]
|
32
|
+
end.join('::')
|
33
|
+
end
|
34
|
+
|
35
|
+
def table_name
|
36
|
+
@table_name ||= begin
|
37
|
+
base = plural_name
|
38
|
+
(class_path + [base]).join('_')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def class_path
|
43
|
+
@class_path
|
44
|
+
end
|
45
|
+
|
46
|
+
def singular_name
|
47
|
+
@file_name
|
48
|
+
end
|
49
|
+
|
50
|
+
def plural_name
|
51
|
+
singular_name.pluralize
|
52
|
+
end
|
53
|
+
|
54
|
+
def assign_names!(name) #:nodoc:
|
55
|
+
@class_path = name.include?('/') ? name.split('/') : name.split('::')
|
56
|
+
@class_path.map!(&:underscore)
|
57
|
+
@file_name = @class_path.pop
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
def invitable_file_path invitable_class_name
|
65
|
+
File.join('app', 'models', "#{invitable_model_name(invitable_class_name).underscore}.rb")
|
66
|
+
end
|
67
|
+
|
68
|
+
def invitable_model_name invitable_class_name
|
69
|
+
if namespaced?
|
70
|
+
[namespace.to_s] + [invitable_class_name.classify]
|
71
|
+
else
|
72
|
+
[invitable_class_name.classify]
|
73
|
+
end.join('::')
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
require 'rails/generators/active_record'
|
3
|
+
require 'generators/invitation/helpers'
|
4
|
+
|
5
|
+
module Invitation
|
6
|
+
module Generators
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
include Rails::Generators::Migration
|
9
|
+
include Invitation::Generators::Helpers
|
10
|
+
|
11
|
+
source_root File.expand_path('../templates', __FILE__)
|
12
|
+
|
13
|
+
class_option :model, optional: true, type: :string, banner: 'model',
|
14
|
+
desc: "Specify the model class name if you will use anything other than 'User'"
|
15
|
+
|
16
|
+
# class_option :invitable, optional: true, type: :array, banner: 'invitable',
|
17
|
+
# desc: "Specify the model class name if you will use anything other than 'User'"
|
18
|
+
|
19
|
+
def verify
|
20
|
+
if options[:model] && !File.exists?(model_path)
|
21
|
+
puts "Exiting: the model class you specified, #{options[:model]}, is not found."
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# def verify_invitable
|
27
|
+
# return unless options[:invitable]
|
28
|
+
# options[:invitable].each do |invitable|
|
29
|
+
# unless File.exists?(invitable_file_path invitable)
|
30
|
+
# puts "Exiting: the model class you specified to make invitable, #{invitable}, is not found."
|
31
|
+
# exit 1
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
|
36
|
+
def inject_into_user_model
|
37
|
+
inject_into_class model_path, model_class_name, " include Invitation::User\n\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
# def inject_into_invitables
|
41
|
+
# return unless options[:invitable]
|
42
|
+
# options[:invitable].each do |invitable|
|
43
|
+
# path = invitable_file_path(invitable)
|
44
|
+
# class_name = invitable.classify
|
45
|
+
# inject_into_class path, class_name, " include Invitation::Invitable\n\n"
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
|
49
|
+
def copy_migration_files
|
50
|
+
copy_migration 'create_invites.rb'
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_initializer
|
54
|
+
copy_file 'invitation.rb', 'config/initializers/invitation.rb'
|
55
|
+
if options[:model]
|
56
|
+
inject_into_file(
|
57
|
+
'config/initializers/invitation.rb',
|
58
|
+
" config.user_model = '#{options[:model]}' \n",
|
59
|
+
after: "Invitation.configure do |config|\n",
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def copy_migration migration_name
|
67
|
+
migration_template "db/migrate/#{migration_name}", "db/migrate/#{migration_name}"
|
68
|
+
end
|
69
|
+
|
70
|
+
# for generating a timestamp when using `create_migration`
|
71
|
+
def self.next_migration_number(dir)
|
72
|
+
ActiveRecord::Generators::Base.next_migration_number(dir)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateInvites < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :invites do |t|
|
4
|
+
t.string :email
|
5
|
+
t.string :token
|
6
|
+
t.integer :sender_id
|
7
|
+
t.integer :recipient_id
|
8
|
+
t.integer :invitable_id
|
9
|
+
t.string :invitable_type
|
10
|
+
t.timestamps
|
11
|
+
t.index :email
|
12
|
+
t.index :token
|
13
|
+
t.index [:invitable_id, :invitable_type]
|
14
|
+
t.index :recipient_id
|
15
|
+
t.index :sender_id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
Invitation.configure do |config|
|
2
|
+
# config.user_model = ::User
|
3
|
+
# config.user_registration_url = ->(params) { Rails.application.routes.url_helpers.sign_up_url(params) }
|
4
|
+
# config.mailer_sender = 'reply@example.com'
|
5
|
+
# config.url_after_invite = '/'
|
6
|
+
# config.routes = true
|
7
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
|
3
|
+
module Invitation
|
4
|
+
module Generators
|
5
|
+
class RoutesGenerator < Rails::Generators::Base
|
6
|
+
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
def add_invitation_routes
|
10
|
+
route(invitation_routes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def disable_invitation_internal_routes
|
14
|
+
inject_into_file(
|
15
|
+
'config/initializers/invitation.rb',
|
16
|
+
" config.routes = false \n",
|
17
|
+
after: "Invitation.configure do |config|\n",
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
|
25
|
+
def invitation_routes
|
26
|
+
File.read(routes_file_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def routes_file_path
|
30
|
+
File.expand_path(find_in_source_paths('routes.rb'))
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
resources :invites, controller: 'invitation/invites', only: [:new, :create]
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Description:
|
2
|
+
Override the default invitation views and locale files. This generator will copy all of the
|
3
|
+
base invitation views and locale files into your project.
|
4
|
+
|
5
|
+
Examples:
|
6
|
+
rails generate invitation:views
|
7
|
+
|
8
|
+
View: app/views/invite_mailer/exiting_user.html.erb
|
9
|
+
View: app/views/invite_mailer/new_user.html.erb
|
10
|
+
View: app/views/invites/new.html.erb
|
11
|
+
Locale: config/locales/en.yml
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
|
3
|
+
#
|
4
|
+
# deploy view and locale assets
|
5
|
+
#
|
6
|
+
module Authenticate
|
7
|
+
module Generators
|
8
|
+
class ViewsGenerator < Rails::Generators::Base
|
9
|
+
source_root File.expand_path("../../../../..", __FILE__)
|
10
|
+
|
11
|
+
def create_views
|
12
|
+
directory 'app/views'
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_locales
|
16
|
+
directory 'config/locales'
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Invitation
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
# ActiveRecord model class name that represents your user.
|
5
|
+
#
|
6
|
+
# Defaults to '::User'.
|
7
|
+
# @return [ActiveRecord::Base]
|
8
|
+
attr_accessor :user_model
|
9
|
+
|
10
|
+
|
11
|
+
# Url for new users to register for your application. New users are invited to
|
12
|
+
# sign up at this url via email. The url should be expressed as a lambda that
|
13
|
+
# accepts one argument, a params hash. This hash will contain the invitation token.
|
14
|
+
#
|
15
|
+
# Defaults to: ->(params) { Rails.application.routes.url_helpers.sign_up_url(params) }
|
16
|
+
#
|
17
|
+
# Note that the default assumes you have `sign_up_url`.
|
18
|
+
#
|
19
|
+
# @return [Lambda]
|
20
|
+
attr_accessor :user_registration_url
|
21
|
+
|
22
|
+
|
23
|
+
# Controls the 'from' address for Invitation emails.
|
24
|
+
# Set this to a value appropriate to your application.
|
25
|
+
#
|
26
|
+
# Defaults to reply@example.com.
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
attr_accessor :mailer_sender
|
30
|
+
|
31
|
+
|
32
|
+
# Path that Invitation will redirect users to after an invitation is issued.
|
33
|
+
#
|
34
|
+
# Defaults to '/'
|
35
|
+
#
|
36
|
+
# Override the invites_controller method if a more complex url is required.
|
37
|
+
# @return [String]
|
38
|
+
attr_accessor :url_after_invite
|
39
|
+
|
40
|
+
|
41
|
+
# Enable or disable Invitation's built-in routes.
|
42
|
+
#
|
43
|
+
# Defaults to 'true'.
|
44
|
+
#
|
45
|
+
# If you disable the routes, your application is responsible for all routes.
|
46
|
+
#
|
47
|
+
# You can deploy a copy of Invitations's routes with `rails generate invitation:routes`,
|
48
|
+
# which will also set `config.routes = false`.
|
49
|
+
#
|
50
|
+
# @return [Boolean]
|
51
|
+
attr_accessor :routes
|
52
|
+
|
53
|
+
|
54
|
+
def initialize
|
55
|
+
@user_model = ::User
|
56
|
+
@user_registration_url = ->(params) { Rails.application.routes.url_helpers.sign_up_url(params) }
|
57
|
+
@mailer_sender = 'reply@example.com'
|
58
|
+
@url_after_invite = '/'
|
59
|
+
@routes = true
|
60
|
+
end
|
61
|
+
|
62
|
+
def user_model_class_name
|
63
|
+
@user_model.name.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
def user_model_instance_var
|
67
|
+
'@' + @user_model.name.demodulize.underscore
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Boolean] are Invitation's built-in routes enabled?
|
71
|
+
def routes_enabled?
|
72
|
+
@routes
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def self.configuration
|
79
|
+
@configuration ||= Configuration.new
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.configuration=(config)
|
83
|
+
@configuration = config
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.configure
|
87
|
+
yield configuration
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#
|
2
|
+
# Any model you wish to invite users to join should extend this concern. This is typically an
|
3
|
+
# organization or resource with limited membership like an "account" or "project".
|
4
|
+
#
|
5
|
+
# Your code is responsible for managing associations between your Invitable and your user model.
|
6
|
+
#
|
7
|
+
# For example, to make the model class Account an organization that can receive an invitation
|
8
|
+
#
|
9
|
+
# class Account < ActiveRecord::Base
|
10
|
+
# invitable named_by: :name
|
11
|
+
#
|
12
|
+
# has_many :account_memberships
|
13
|
+
# has_many :users, through: :account_memberships
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
#
|
17
|
+
module Invitation
|
18
|
+
module Invitable
|
19
|
+
|
20
|
+
def invitable(options = {})
|
21
|
+
has_many :invites, as: :invitable
|
22
|
+
class_attribute :invitable_options
|
23
|
+
|
24
|
+
self.invitable_options = options.dup # named_by: :name, named: 'Gug'
|
25
|
+
self.invitable_options[:named_by] = options[:named_by] if options[:named_by]
|
26
|
+
self.invitable_options[:named] = options[:named] if options[:named]
|
27
|
+
|
28
|
+
unless self.invitable_options[:named] || self.invitable_options[:named_by]
|
29
|
+
raise <<-eos
|
30
|
+
invitable requires options be set, either :name or :named_by.
|
31
|
+
invitable named: "string"
|
32
|
+
or
|
33
|
+
invitable named_by: :method_name
|
34
|
+
eos
|
35
|
+
end
|
36
|
+
|
37
|
+
include Invitation::Invitable::InstanceMethods
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
module InstanceMethods
|
42
|
+
|
43
|
+
# Add the invited user to the organization. Called by InvitesController.
|
44
|
+
def add_invited_user(user)
|
45
|
+
method = Invitation.configuration.user_model.name.underscore.pluralize
|
46
|
+
self.send(method).push(user)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get the name of the organization for use in invitations.
|
50
|
+
def invitable_name
|
51
|
+
if invitable_options[:named_by]
|
52
|
+
self.send(invitable_options[:named_by])
|
53
|
+
elsif invitable_options[:named]
|
54
|
+
invitable_options[:named]
|
55
|
+
else
|
56
|
+
raise 'Invitation runtime error: invitable does not have name: or named_by: set, should not be possible! ' +
|
57
|
+
self.inspect
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#
|
2
|
+
# Your user model must include this concern to send and receive invitations. Your user class must also be
|
3
|
+
# specified in the invitation configuration `Invitation.configure.user_model`.
|
4
|
+
#
|
5
|
+
# Your user model code is responsible for managing associations to any organizations you wish
|
6
|
+
# to issue invitations to. Your user model will probably also include an authentication model.
|
7
|
+
#
|
8
|
+
# For example, to make your user class `User` able to issue invitations to model `Account`:
|
9
|
+
#
|
10
|
+
# class User < ActiveRecord::Base
|
11
|
+
# include Invitation::User
|
12
|
+
# include Authenticate::User
|
13
|
+
#
|
14
|
+
# has_many :account_memberships
|
15
|
+
# has_many :accounts, through: :account_memberships
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
module Invitation
|
19
|
+
module User
|
20
|
+
extend ActiveSupport::Concern
|
21
|
+
|
22
|
+
included do
|
23
|
+
has_many :invitations, class_name: 'Invite', foreign_key: :recipient_id
|
24
|
+
has_many :sent_invites, class_name: 'Invite', foreign_key: :sender_id
|
25
|
+
end
|
26
|
+
|
27
|
+
def claim_invite(token)
|
28
|
+
invite = Invite.find_by_token(token)
|
29
|
+
return if invite.nil?
|
30
|
+
invitable = invite.invitable
|
31
|
+
invitable.add_invited_user self
|
32
|
+
invite.recipient = self
|
33
|
+
invite.save
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Your user registration controller should include this concern.
|
2
|
+
#
|
3
|
+
module Invitation
|
4
|
+
module UserRegistration
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
|
8
|
+
# Copy params[:invite_token] to @invite_token. Your user registration form needs
|
9
|
+
# to include :invite_token, this method is the controller half of the glue.
|
10
|
+
#
|
11
|
+
# Use this in your user registration controller in a before_action for the new action.
|
12
|
+
#
|
13
|
+
# before_action :set_token, only: [:new]
|
14
|
+
#
|
15
|
+
def set_invite_token
|
16
|
+
@invite_token = params[:invite_token]
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Check for an invitation token and process the invite. If an invitation is found, the
|
21
|
+
# user claims the invite.
|
22
|
+
#
|
23
|
+
# Use this only when creating a new user. Invoke manually or from an after_action:
|
24
|
+
#
|
25
|
+
# after_action :process_invite, only: [:create]
|
26
|
+
#
|
27
|
+
# Invoke with new_user, or set an instance variable with the standard 'underscore' name of your user model class.
|
28
|
+
# For example, if your user model is UserProfile, this method will check for @user_profile.
|
29
|
+
#
|
30
|
+
def process_invite_token(new_user = nil)
|
31
|
+
if new_user.nil?
|
32
|
+
new_user = user_instance_variable
|
33
|
+
end
|
34
|
+
|
35
|
+
token = params[:invite_token]
|
36
|
+
if token != nil && new_user != nil
|
37
|
+
new_user.claim_invite token
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
|
45
|
+
def user_instance_variable
|
46
|
+
name = Invitation.configuration.user_model_instance_var
|
47
|
+
self.instance_variable_get(name)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
data/lib/invitation.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'invitation/engine'
|
2
|
+
require 'invitation/configuration'
|
3
|
+
require 'invitation/user'
|
4
|
+
require 'invitation/invitable'
|
5
|
+
require 'invitation/user_registration'
|
6
|
+
|
7
|
+
module Invitation
|
8
|
+
end
|
9
|
+
|
10
|
+
# ActiveRecord::Base.send :include, Invitation::Invitable
|
11
|
+
ActiveRecord::Base.extend Invitation::Invitable
|
metadata
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: invitation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Tomich
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '5.1'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5.1'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: factory_girl
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '4.4'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '4.4'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec-rails
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.1'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.1'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec-mocks
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.1'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.1'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: pry
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0.10'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0.10'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: sqlite3
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.3'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '1.3'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: shoulda-matchers
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.8'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '2.8'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: capybara
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '2.6'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '2.6'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: database_cleaner
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '1.5'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '1.5'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: timecop
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0.8'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0.8'
|
159
|
+
description: Allow users to invite other users to join your organization
|
160
|
+
email:
|
161
|
+
- justin@tomich.org
|
162
|
+
executables: []
|
163
|
+
extensions: []
|
164
|
+
extra_rdoc_files: []
|
165
|
+
files:
|
166
|
+
- Rakefile
|
167
|
+
- app/controllers/invitation/invites_controller.rb
|
168
|
+
- app/mailers/invite_mailer.rb
|
169
|
+
- app/models/invite.rb
|
170
|
+
- app/views/invite_mailer/existing_user.html.erb
|
171
|
+
- app/views/invite_mailer/new_user.html.erb
|
172
|
+
- app/views/invites/new.html.erb
|
173
|
+
- config/locales/invitation.en.yml
|
174
|
+
- config/routes.rb
|
175
|
+
- lib/generators/invitation/controllers/USAGE
|
176
|
+
- lib/generators/invitation/controllers/controllers_generator.rb
|
177
|
+
- lib/generators/invitation/helpers.rb
|
178
|
+
- lib/generators/invitation/install/USAGE
|
179
|
+
- lib/generators/invitation/install/install_generator.rb
|
180
|
+
- lib/generators/invitation/install/templates/db/migrate/create_invites.rb
|
181
|
+
- lib/generators/invitation/install/templates/invitation.rb
|
182
|
+
- lib/generators/invitation/routes/routes_generator.rb
|
183
|
+
- lib/generators/invitation/routes/templates/routes.rb
|
184
|
+
- lib/generators/invitation/views/USAGE
|
185
|
+
- lib/generators/invitation/views/views_generator.rb
|
186
|
+
- lib/invitation.rb
|
187
|
+
- lib/invitation/configuration.rb
|
188
|
+
- lib/invitation/engine.rb
|
189
|
+
- lib/invitation/invitable.rb
|
190
|
+
- lib/invitation/user.rb
|
191
|
+
- lib/invitation/user_registration.rb
|
192
|
+
- lib/invitation/version.rb
|
193
|
+
- lib/tasks/invitation_tasks.rake
|
194
|
+
homepage: http://github.com/tomichj/invitation
|
195
|
+
licenses:
|
196
|
+
- MIT
|
197
|
+
metadata: {}
|
198
|
+
post_install_message:
|
199
|
+
rdoc_options: []
|
200
|
+
require_paths:
|
201
|
+
- lib
|
202
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
203
|
+
requirements:
|
204
|
+
- - ">="
|
205
|
+
- !ruby/object:Gem::Version
|
206
|
+
version: '2.0'
|
207
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
208
|
+
requirements:
|
209
|
+
- - ">="
|
210
|
+
- !ruby/object:Gem::Version
|
211
|
+
version: '0'
|
212
|
+
requirements: []
|
213
|
+
rubyforge_project:
|
214
|
+
rubygems_version: 2.5.1
|
215
|
+
signing_key:
|
216
|
+
specification_version: 4
|
217
|
+
summary: Allow users to invite other users to join your organization
|
218
|
+
test_files: []
|