devise_invitable 2.0.5 → 2.0.7
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of devise_invitable might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.rdoc +61 -4
- data/app/controllers/devise/invitations_controller.rb +3 -3
- data/config/locales/ca.yml +32 -0
- data/config/locales/de.yml +31 -31
- data/config/locales/es.yml +1 -1
- data/lib/devise_invitable/controllers/helpers.rb +4 -0
- data/lib/devise_invitable/mapping.rb +4 -2
- data/lib/devise_invitable/models.rb +3 -2
- data/lib/devise_invitable/version.rb +1 -1
- data/lib/generators/devise_invitable/devise_invitable_generator.rb +1 -1
- data/test/functional/controller_helpers_test.rb +10 -0
- data/test/mailers/invitation_mail_test.rb +1 -1
- data/test/model_tests_helper.rb +1 -1
- data/test/models/invitable_test.rb +2 -2
- data/test/orm/active_record.rb +6 -1
- data/test/test_helper.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2a7d1c4ae3cc61e2992fa9920ddd35e6626745dd32333a88e8231fb267e2cb0
|
4
|
+
data.tar.gz: ee68fbb505a629c391934bee8c644c0ade499333d5c7065745426fd6e419642a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd3ead62e8c2246f9246fbac3a2138ea6ff4f1abcf3c577faad920b339f1c1795b54497d03b34f4bc685be6131f62692d2497fbc5d1081e740e0a85d7e1a25c2
|
7
|
+
data.tar.gz: 0453554d47731a99e931c81dbd44e77eca935ff6ca453172f40f394da84bd1d5a92507aafa881ab491202cee5fa534aef4ba9b2a4ac1fa37327a664c7ceb8e61
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
- Allow customizing invalid_token_path_for, the path to redirect users who try to accept with invalid token
|
2
|
+
- Don't override registrations controller in routes if module option is used
|
3
|
+
- Fix typo in spanish translation, add Catalan translation ([#857](https://github.com/scambra/devise_invitable/pull/857))
|
4
|
+
- Fix for ruby 3.2.0
|
5
|
+
|
6
|
+
## 2.0.6
|
7
|
+
- Fix submit form failure with turbolinks, fixes ([#865](https://github.com/scambra/devise_invitable/issues/865))
|
8
|
+
- Fix obsolete symbols in German translation ([#864](https://github.com/scambra/devise_invitable/pull/864))
|
9
|
+
- Allow to provide validate option to the instance method "invite!", default to follow the setting validate_on_invite
|
10
|
+
|
1
11
|
## 2.0.5
|
2
12
|
- Fix NoMethodError in random_password when validatable is not used ([#850](https://github.com/scambra/devise_invitable/pull/850))
|
3
13
|
|
data/README.rdoc
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
= DeviseInvitable
|
2
|
-
{<img src="https://badge.fury.io/rb/devise_invitable.svg"/>}[http://badge.fury.io/rb/devise_invitable]
|
2
|
+
{<img src="https://badge.fury.io/rb/devise_invitable.svg"/>}[http://badge.fury.io/rb/devise_invitable]
|
3
|
+
{<img src="https://github.com/scambra/devise_invitable/actions/workflows/ci.yml/badge.svg"/>}[https://github.com/scambra/devise_invitable/actions/workflows/ci.yml]
|
4
|
+
{<img src="https://codeclimate.com/github/scambra/devise_invitable/badges/gpa.svg"/>}[https://codeclimate.com/github/scambra/devise_invitable]
|
3
5
|
|
4
6
|
It adds support to Devise[https://github.com/plataformatec/devise] for sending invitations by email (it requires to be authenticated) and accept the invitation setting the password.
|
5
7
|
|
@@ -35,14 +37,14 @@ Replace MODEL by the class name you want to add DeviseInvitable, like <tt>User</
|
|
35
37
|
|
36
38
|
Follow the walkthrough for Devise and after it's done, follow this walkthrough.
|
37
39
|
|
38
|
-
|
40
|
+
==== Devise Configuration
|
39
41
|
Add <tt>:invitable</tt> to the <tt>devise</tt> call in your model (we’re assuming here you already have a User model with some Devise modules):
|
40
42
|
|
41
43
|
class User < ActiveRecord::Base
|
42
44
|
devise :database_authenticatable, :confirmable, :invitable
|
43
45
|
end
|
44
46
|
|
45
|
-
|
47
|
+
==== ActiveRecord Migration
|
46
48
|
Add <tt>t.invitable</tt> to your Devise model migration:
|
47
49
|
|
48
50
|
create_table :users do
|
@@ -217,6 +219,61 @@ Here is an example of what your application controller might need to include in
|
|
217
219
|
devise_parameter_sanitizer.permit(:accept_invitation, keys: [:first_name, :last_name, :phone])
|
218
220
|
end
|
219
221
|
|
222
|
+
Here is an example setting a User's first name, last name, and role for a custom invitation:
|
223
|
+
|
224
|
+
#Configuring the InvitationsController to accept :first_name, :last_name, and :role
|
225
|
+
|
226
|
+
class Users::InvitationsController < Devise::InvitationsController
|
227
|
+
before_action :configure_permitted_parameters
|
228
|
+
|
229
|
+
protected
|
230
|
+
|
231
|
+
# Permit the new params here.
|
232
|
+
def configure_permitted_parameters
|
233
|
+
devise_parameter_sanitizer.permit(:invite, keys: [:first_name, :last_name, :role])
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
#Define your roles in the User model
|
238
|
+
|
239
|
+
class User < ApplicationRecord
|
240
|
+
has_many :models
|
241
|
+
|
242
|
+
enum role: {Role 1 Name: 0, Role 2 Name: 1, Role 3 Name: 2, etc...}
|
243
|
+
end
|
244
|
+
|
245
|
+
#In the Invitation view
|
246
|
+
|
247
|
+
<h2><%= t "devise.invitations.new.header" %></h2>
|
248
|
+
|
249
|
+
<%= form_for(resource, as: resource_name, url: invitation_path(resource_name), html: { method: :post }) do |f| %>
|
250
|
+
<%= render "devise/shared/error_messages", resource: resource %>
|
251
|
+
<% resource.class.invite_key_fields.each do |field| -%>
|
252
|
+
<div class="field">
|
253
|
+
<%= f.label field %><br />
|
254
|
+
<%= f.text_field field %>
|
255
|
+
</div>
|
256
|
+
<% end %>
|
257
|
+
|
258
|
+
<div class="field">
|
259
|
+
<%= f.label :first_name %>
|
260
|
+
<%= f.text_field :first_name %>
|
261
|
+
</div>
|
262
|
+
|
263
|
+
<div class="field">
|
264
|
+
<%= f.label :last_name %>
|
265
|
+
<%= f.text_field :last_name %>
|
266
|
+
</div>
|
267
|
+
|
268
|
+
<div class="field">
|
269
|
+
<%= f.label :role %>
|
270
|
+
<%= f.select :role, options_for_select(User.roles.map { |key, value| [key.humanize, key] }), {prompt: "Select Role"} %>
|
271
|
+
</div>
|
272
|
+
|
273
|
+
<div class="actions">
|
274
|
+
<%= f.submit t("devise.invitations.new.submit_button") %>
|
275
|
+
</div>
|
276
|
+
<% end %>
|
220
277
|
|
221
278
|
== Usage
|
222
279
|
|
@@ -315,7 +372,7 @@ After an invitation is created and sent, the inviter will be redirected to <tt>a
|
|
315
372
|
|
316
373
|
After an invitation is accepted, the invitee will be redirected to <tt>after_accept_path_for(resource)</tt>, which is the same path as <tt>signed_in_root_path</tt> by default. If you want to override the path, override invitations controller and define <tt>after_accept_path_for</tt> method. This is useful in the common case that a user is invited to a specific location in your application. More on {Devise's README}[https://github.com/plataformatec/devise], "Controller filters and helpers" section.
|
317
374
|
|
318
|
-
The invitation email includes a link to accept the invitation that looks like this: <tt>/users/invitation/accept?invitation_token=abcd123</tt>. When clicked, the invited must set a password in order to accept its invitation. Note that if the <tt>invitation_token</tt> is not present or not valid, the invited is redirected to <tt>after_sign_out_path_for(resource_name)</tt>.
|
375
|
+
The invitation email includes a link to accept the invitation that looks like this: <tt>/users/invitation/accept?invitation_token=abcd123</tt>. When clicked, the invited must set a password in order to accept its invitation. Note that if the <tt>invitation_token</tt> is not present or not valid, the invited is redirected to <tt>invalid_token_path_for(resource_name)</tt>, which by default is <tt>after_sign_out_path_for(resource_name)</tt>.
|
319
376
|
|
320
377
|
The controller sets the <tt>invited_by_id</tt> attribute for the new user to the current user. This will let you easily keep track of who invited whom.
|
321
378
|
|
@@ -31,7 +31,7 @@ class Devise::InvitationsController < DeviseController
|
|
31
31
|
respond_with resource, location: after_invite_path_for(current_inviter, resource)
|
32
32
|
end
|
33
33
|
else
|
34
|
-
respond_with_navigational(resource) { render :new }
|
34
|
+
respond_with_navigational(resource) { render :new, status: :unprocessable_entity }
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -63,7 +63,7 @@ class Devise::InvitationsController < DeviseController
|
|
63
63
|
end
|
64
64
|
else
|
65
65
|
resource.invitation_token = raw_invitation_token
|
66
|
-
respond_with_navigational(resource) { render :edit }
|
66
|
+
respond_with_navigational(resource) { render :edit, status: :unprocessable_entity }
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -99,7 +99,7 @@ class Devise::InvitationsController < DeviseController
|
|
99
99
|
def resource_from_invitation_token
|
100
100
|
unless params[:invitation_token] && self.resource = resource_class.find_by_invitation_token(params[:invitation_token], true)
|
101
101
|
set_flash_message(:alert, :invitation_token_invalid) if is_flashing_format?
|
102
|
-
redirect_to
|
102
|
+
redirect_to invalid_token_path_for(resource_name)
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
ca:
|
3
|
+
devise:
|
4
|
+
failure:
|
5
|
+
invited: "Tens una invitació pendent, accepta-la per acabar de crear el teu compte."
|
6
|
+
invitations:
|
7
|
+
send_instructions: "S'ha enviat una invitació a %{email}."
|
8
|
+
invitation_token_invalid: "La invitació no es vàlida!"
|
9
|
+
updated: "S'ha configurat la seva contrasenya i s'ha ingressat al sistema"
|
10
|
+
updated_not_active: "La seva contrasenya s'ha configurat correctament."
|
11
|
+
no_invitations_remaining: "No queden invitacions"
|
12
|
+
invitation_removed: "S'ha retirat la seva invitació"
|
13
|
+
new:
|
14
|
+
header: "Enviar Invitació"
|
15
|
+
submit_button: "Envia una invitació"
|
16
|
+
edit:
|
17
|
+
header: "Establir contrasenya"
|
18
|
+
submit_button: "Guardar la meva contrasenya"
|
19
|
+
mailer:
|
20
|
+
invitation_instructions:
|
21
|
+
subject: "Instruccions de la invitació"
|
22
|
+
hello: "Hola %{email}"
|
23
|
+
someone_invited_you: "Has estat invitat a %{url}, pots acceptar-ho seguint el següent enllaç"
|
24
|
+
accept: "Aceptar la invitació"
|
25
|
+
accept_until: "Aquesta invitació expirarà en %{due_date}."
|
26
|
+
ignore: "Si no li interessa aquesta invitació, simplement ignori aquest correu. No es crearà el teu compte fins que accedeixis a l'anterior enllaç i creïs una contrasenya"
|
27
|
+
time:
|
28
|
+
formats:
|
29
|
+
devise:
|
30
|
+
mailer:
|
31
|
+
invitation_instructions:
|
32
|
+
accept_until_format: "%d de %B de %Y, %H:%M"
|
data/config/locales/de.yml
CHANGED
@@ -1,31 +1,31 @@
|
|
1
|
-
de:
|
2
|
-
devise:
|
3
|
-
failure:
|
4
|
-
invited: "Du hast bereits eine Einladung erhalten. Nimm die Einladung an um dein Nutzerkonto zu erstellen."
|
5
|
-
invitations:
|
6
|
-
send_instructions: "Eine Einladung wurde an %{email} verschickt."
|
7
|
-
invitation_token_invalid: "Der Einladungs-Code ist ungültig!"
|
8
|
-
updated: "Dein Passwort wurde gesetzt. Du bist nun angemeldet."
|
9
|
-
updated_not_active: "Dein Passwort wurde gesetzt."
|
10
|
-
no_invitations_remaining: "Es gibt keine weiteren Einladungen"
|
11
|
-
invitation_removed: "Deine Einladung wurde gelöscht."
|
12
|
-
new:
|
13
|
-
header: "Einladung schicken"
|
14
|
-
submit_button: "Einladung schicken"
|
15
|
-
edit:
|
16
|
-
header: "Setze ein Passwort"
|
17
|
-
submit_button: "Passwort setzen"
|
18
|
-
mailer:
|
19
|
-
invitation_instructions:
|
20
|
-
subject: "Einladung"
|
21
|
-
hello: "Hallo %{email}"
|
22
|
-
someone_invited_you: "Jemand hat dich zu %{url} eingeladen. Du kannst die Einladung mit dem Link unten annehmen."
|
23
|
-
accept: "Einladung annehmen"
|
24
|
-
accept_until: "Diese Einladung ist gültig bis zum %{due_date}."
|
25
|
-
ignore: "Wenn du die Einladung nicht annehmen willst, ignoriere diese E-Mail einfach
|
26
|
-
time:
|
27
|
-
formats:
|
28
|
-
devise:
|
29
|
-
mailer:
|
30
|
-
invitation_instructions:
|
31
|
-
accept_until_format: "%d. %B %Y %H:%M"
|
1
|
+
de:
|
2
|
+
devise:
|
3
|
+
failure:
|
4
|
+
invited: "Du hast bereits eine Einladung erhalten. Nimm die Einladung an um dein Nutzerkonto zu erstellen."
|
5
|
+
invitations:
|
6
|
+
send_instructions: "Eine Einladung wurde an %{email} verschickt."
|
7
|
+
invitation_token_invalid: "Der Einladungs-Code ist ungültig!"
|
8
|
+
updated: "Dein Passwort wurde gesetzt. Du bist nun angemeldet."
|
9
|
+
updated_not_active: "Dein Passwort wurde gesetzt."
|
10
|
+
no_invitations_remaining: "Es gibt keine weiteren Einladungen"
|
11
|
+
invitation_removed: "Deine Einladung wurde gelöscht."
|
12
|
+
new:
|
13
|
+
header: "Einladung schicken"
|
14
|
+
submit_button: "Einladung schicken"
|
15
|
+
edit:
|
16
|
+
header: "Setze ein Passwort"
|
17
|
+
submit_button: "Passwort setzen"
|
18
|
+
mailer:
|
19
|
+
invitation_instructions:
|
20
|
+
subject: "Einladung"
|
21
|
+
hello: "Hallo %{email}"
|
22
|
+
someone_invited_you: "Jemand hat dich zu %{url} eingeladen. Du kannst die Einladung mit dem Link unten annehmen."
|
23
|
+
accept: "Einladung annehmen"
|
24
|
+
accept_until: "Diese Einladung ist gültig bis zum %{due_date}."
|
25
|
+
ignore: "Wenn du die Einladung nicht annehmen willst, ignoriere diese E-Mail einfach. Es wird kein Benutzerkonto erstellt solange du nicht die Einladung mittels des Links oben annimmst."
|
26
|
+
time:
|
27
|
+
formats:
|
28
|
+
devise:
|
29
|
+
mailer:
|
30
|
+
invitation_instructions:
|
31
|
+
accept_until_format: "%d. %B %Y %H:%M"
|
data/config/locales/es.yml
CHANGED
@@ -17,7 +17,7 @@ es:
|
|
17
17
|
submit_button: "Guardar mi contraseña"
|
18
18
|
mailer:
|
19
19
|
invitation_instructions:
|
20
|
-
subject: "
|
20
|
+
subject: "Instrucciones de la invitación"
|
21
21
|
hello: "Hola %{email}"
|
22
22
|
someone_invited_you: "Has sido invitado a %{url}, puedes aceptarlo siguiendo el siguiente enlace"
|
23
23
|
accept: "Aceptar la invitación"
|
@@ -3,8 +3,10 @@ module DeviseInvitable
|
|
3
3
|
private
|
4
4
|
|
5
5
|
def default_controllers(options)
|
6
|
-
options[:
|
7
|
-
|
6
|
+
unless options[:module]
|
7
|
+
options[:controllers] ||= {}
|
8
|
+
options[:controllers][:registrations] ||= 'devise_invitable/registrations'
|
9
|
+
end
|
8
10
|
super
|
9
11
|
end
|
10
12
|
end
|
@@ -158,7 +158,8 @@ module Devise
|
|
158
158
|
self.downcase_keys if new_record_and_responds_to?(:downcase_keys)
|
159
159
|
self.strip_whitespace if new_record_and_responds_to?(:strip_whitespace)
|
160
160
|
|
161
|
-
|
161
|
+
validate = options.key?(:validate) ? options[:validate] : self.class.validate_on_invite
|
162
|
+
if save(validate: validate)
|
162
163
|
self.invited_by.decrement_invitation_limit! if !was_invited and self.invited_by.present?
|
163
164
|
deliver_invitation(options) unless skip_invitation
|
164
165
|
end
|
@@ -324,7 +325,7 @@ module Devise
|
|
324
325
|
end
|
325
326
|
|
326
327
|
yield invitable if block_given?
|
327
|
-
mail = invitable.invite!(nil, options) if invitable.errors.empty?
|
328
|
+
mail = invitable.invite!(nil, options.merge(validate: false)) if invitable.errors.empty?
|
328
329
|
[invitable, mail]
|
329
330
|
end
|
330
331
|
|
@@ -7,7 +7,7 @@ module DeviseInvitable
|
|
7
7
|
|
8
8
|
def inject_devise_invitable_content
|
9
9
|
path = File.join('app', 'models', "#{file_path}.rb")
|
10
|
-
inject_into_file(path, 'invitable, :', after: 'devise :') if File.
|
10
|
+
inject_into_file(path, 'invitable, :', after: 'devise :') if File.exist?(path)
|
11
11
|
end
|
12
12
|
|
13
13
|
hook_for :orm
|
@@ -41,4 +41,14 @@ class ControllerHelpersTest < ActionController::TestCase
|
|
41
41
|
assert Devise::InvitationsController.method_defined? :after_accept_path_for
|
42
42
|
assert !Devise::InvitationsController.instance_methods(false).include?(:after_accept_path_for)
|
43
43
|
end
|
44
|
+
|
45
|
+
test 'invalid token path defaults to after sign out path' do
|
46
|
+
assert_equal @controller.send(:after_sign_out_path_for, :user), @controller.invalid_token_path_for(:user)
|
47
|
+
end
|
48
|
+
|
49
|
+
test 'invalid token path is customizable from application controller' do
|
50
|
+
custom_path = 'customized/invalid/token/path'
|
51
|
+
@controller.instance_eval "def invalid_token_path_for(resource_name) '#{custom_path}' end"
|
52
|
+
assert_equal @controller.invalid_token_path_for(:user), custom_path
|
53
|
+
end
|
44
54
|
end
|
@@ -102,7 +102,7 @@ class InvitationMailTest < ActionMailer::TestCase
|
|
102
102
|
def initialize(*args); end
|
103
103
|
def deliver; end
|
104
104
|
end
|
105
|
-
Devise.mailer = CustomMailer
|
105
|
+
Devise.mailer = 'InvitationMailTest::CustomMailer'
|
106
106
|
|
107
107
|
User.invite!({ email: 'valid@email.com' }, nil, { invited_at: Time.now })
|
108
108
|
end
|
data/test/model_tests_helper.rb
CHANGED
@@ -89,12 +89,12 @@ class InvitableTest < ActiveSupport::TestCase
|
|
89
89
|
user.invite!
|
90
90
|
old_invitation_created_at = 3.days.ago
|
91
91
|
old_invitation_sent_at = 3.days.ago
|
92
|
-
user.
|
92
|
+
user.update(invitation_sent_at: old_invitation_sent_at, invitation_created_at: old_invitation_created_at)
|
93
93
|
3.times do
|
94
94
|
user.invite!
|
95
95
|
refute_equal old_invitation_sent_at, user.invitation_sent_at
|
96
96
|
refute_equal old_invitation_created_at, user.invitation_created_at
|
97
|
-
user.
|
97
|
+
user.update(invitation_sent_at: old_invitation_sent_at, invitation_created_at: old_invitation_created_at)
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
data/test/orm/active_record.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
ActiveRecord::Migration.verbose = false
|
2
2
|
ActiveRecord::Base.logger = Logger.new(nil)
|
3
3
|
|
4
|
-
if
|
4
|
+
if ActiveRecord::VERSION::MAJOR >= 6
|
5
|
+
ActiveRecord::MigrationContext.new(
|
6
|
+
File.expand_path('../../rails_app/db/migrate/', __FILE__),
|
7
|
+
ActiveRecord::Base.connection.schema_migration
|
8
|
+
).migrate
|
9
|
+
elsif defined? ActiveRecord::MigrationContext # rails >= 5.2
|
5
10
|
ActiveRecord::MigrationContext.new(File.expand_path('../../rails_app/db/migrate/', __FILE__)).migrate
|
6
11
|
else
|
7
12
|
ActiveRecord::Migrator.migrate(File.expand_path('../../rails_app/db/migrate/', __FILE__))
|
data/test/test_helper.rb
CHANGED
@@ -7,7 +7,7 @@ require "rails_app/config/environment"
|
|
7
7
|
require 'rails/test_help'
|
8
8
|
require "orm/#{DEVISE_ORM}"
|
9
9
|
require 'capybara/rails'
|
10
|
-
require 'mocha/
|
10
|
+
require 'mocha/minitest'
|
11
11
|
|
12
12
|
ActionMailer::Base.delivery_method = :test
|
13
13
|
ActionMailer::Base.perform_deliveries = true
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devise_invitable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergio Cambra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionmailer
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- app/views/devise/mailer/invitation_instructions.html.erb
|
71
71
|
- app/views/devise/mailer/invitation_instructions.text.erb
|
72
72
|
- config/locales/ar.yml
|
73
|
+
- config/locales/ca.yml
|
73
74
|
- config/locales/da.yml
|
74
75
|
- config/locales/de.yml
|
75
76
|
- config/locales/en.yml
|
@@ -186,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
187
|
- !ruby/object:Gem::Version
|
187
188
|
version: '0'
|
188
189
|
requirements: []
|
189
|
-
rubygems_version: 3.
|
190
|
+
rubygems_version: 3.3.7
|
190
191
|
signing_key:
|
191
192
|
specification_version: 4
|
192
193
|
summary: An invitation strategy for Devise
|