devise_invitable 2.0.13 → 3.0.0

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +108 -97
  4. data/app/controllers/devise/invitations_controller.rb +1 -1
  5. data/app/controllers/devise_invitable/registrations_controller.rb +15 -10
  6. data/lib/devise_invitable/invitation.rb +89 -0
  7. data/lib/devise_invitable/inviter.rb +23 -1
  8. data/lib/devise_invitable/migrator.rb +113 -0
  9. data/lib/devise_invitable/models.rb +328 -81
  10. data/lib/devise_invitable/rails.rb +15 -16
  11. data/lib/devise_invitable/version.rb +1 -1
  12. data/lib/devise_invitable.rb +39 -4
  13. data/lib/generators/active_record/devise_invitable_install_generator.rb +22 -0
  14. data/lib/generators/active_record/devise_invitable_upgrade_generator.rb +28 -0
  15. data/lib/generators/active_record/templates/create_devise_invitations.rb +23 -0
  16. data/lib/generators/active_record/templates/upgrade_devise_invitations.rb +72 -0
  17. data/lib/generators/devise_invitable/devise_invitable_generator.rb +2 -4
  18. data/lib/generators/devise_invitable/install_generator.rb +8 -10
  19. data/lib/generators/devise_invitable/upgrade_generator.rb +16 -0
  20. data/lib/generators/mongoid/devise_invitable_install_generator.rb +7 -0
  21. data/lib/generators/mongoid/devise_invitable_upgrade_generator.rb +11 -0
  22. data/lib/tasks/devise_invitable_tasks.rake +10 -0
  23. data/test/functional/registrations_controller_test.rb +24 -3
  24. data/test/generators_test.rb +16 -9
  25. data/test/integration/invitation_test.rb +3 -3
  26. data/test/mailers/invitation_mail_test.rb +1 -0
  27. data/test/mailers/parent_mailer_test.rb +31 -0
  28. data/test/models/separate_invitations_test.rb +181 -0
  29. data/test/models_test.rb +34 -0
  30. data/test/rails_app/app/mailers/application_mailer.rb +2 -0
  31. data/test/rails_app/app/models/user.rb +2 -1
  32. data/test/rails_app/config/initializers/devise.rb +0 -9
  33. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +22 -0
  34. metadata +26 -13
  35. data/lib/generators/active_record/devise_invitable_generator.rb +0 -19
  36. data/lib/generators/active_record/templates/migration.rb +0 -22
  37. data/lib/generators/mongoid/devise_invitable_generator.rb +0 -8
@@ -1,6 +1,10 @@
1
1
  module DeviseInvitable
2
+ class ResourceAttributesRequirePlaceholderError < ArgumentError; end
3
+
2
4
  autoload :Inviter, 'devise_invitable/inviter'
5
+ autoload :Invitation, 'devise_invitable/invitation'
3
6
  autoload :Mailer, 'devise_invitable/mailer'
7
+ autoload :Migrator, 'devise_invitable/migrator'
4
8
  autoload :Mapping, 'devise_invitable/mapping'
5
9
  autoload :ParameterSanitizer, 'devise_invitable/parameter_sanitizer'
6
10
  module Controllers
@@ -10,6 +14,7 @@ module DeviseInvitable
10
14
  end
11
15
 
12
16
  require 'devise'
17
+ require 'active_support/deprecation'
13
18
  require 'devise_invitable/routes'
14
19
  require 'devise_invitable/rails'
15
20
 
@@ -58,16 +63,26 @@ module Devise
58
63
  mattr_accessor :resend_invitation
59
64
  @@resend_invitation = true
60
65
 
61
- # Public: The class name of the inviting model. If this is nil,
62
- # the #invited_by association is declared to be polymorphic. (default: nil)
66
+ # Deprecated compatibility setting. Invitations now always use a polymorphic
67
+ # invited_by association.
63
68
  mattr_accessor :invited_by_class_name
64
69
  @@invited_by_class_name = nil
65
70
 
66
- # Public: The foreign key to the inviting model (if invited_by_class_name is set)
67
- # (default: :invited_by_id)
71
+ def self.invited_by_class_name=(value)
72
+ DeviseInvitable.warn_deprecated_invited_by_setting(:invited_by_class_name)
73
+ @@invited_by_class_name = value
74
+ end
75
+
76
+ # Deprecated compatibility setting. The shared invitations table always uses
77
+ # invited_by_id and invited_by_type.
68
78
  mattr_accessor :invited_by_foreign_key
69
79
  @@invited_by_foreign_key = nil
70
80
 
81
+ def self.invited_by_foreign_key=(value)
82
+ DeviseInvitable.warn_deprecated_invited_by_setting(:invited_by_foreign_key)
83
+ @@invited_by_foreign_key = value
84
+ end
85
+
71
86
  # Public: The column name used for counter_cache column. If this is nil,
72
87
  # the #invited_by association is declared without counter_cache. (default: nil)
73
88
  mattr_accessor :invited_by_counter_cache
@@ -83,6 +98,26 @@ module Devise
83
98
  # set when user is invited or it will be set later (default: true).
84
99
  mattr_accessor :require_password_on_accepting
85
100
  @@require_password_on_accepting = true
101
+
102
+ # Public: Create the invited resource as soon as an invitation is sent.
103
+ # When false, only the invitation and its invite keys are persisted and the
104
+ # resource is created when the invitation is accepted.
105
+ mattr_accessor :create_resource_on_invitation
106
+ @@create_resource_on_invitation = false
107
+ end
108
+
109
+ module DeviseInvitable
110
+ def self.deprecator
111
+ @deprecator ||= ActiveSupport::Deprecation.new('3.0', 'DeviseInvitable')
112
+ end
113
+
114
+ def self.warn_deprecated_invited_by_setting(setting)
115
+ deprecator.warn(
116
+ "The #{setting} setting is deprecated and has no effect. " \
117
+ "Invitations now always use a polymorphic invited_by association " \
118
+ "with invited_by_id and invited_by_type."
119
+ )
120
+ end
86
121
  end
87
122
 
88
123
  Devise.add_module :invitable, controller: :invitations, model: 'devise_invitable/models', route: { invitation: [nil, :new, :accept] }
@@ -0,0 +1,22 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ActiveRecord
4
+ module Generators
5
+ class DeviseInvitableInstallGenerator < ActiveRecord::Generators::Base
6
+ source_root File.expand_path('../templates', __FILE__)
7
+ argument :name, type: :string, default: 'devise_invitation'
8
+
9
+ def copy_devise_invitations_migration
10
+ migration_template 'create_devise_invitations.rb',
11
+ 'db/migrate/create_devise_invitations.rb',
12
+ migration_version: migration_version
13
+ end
14
+
15
+ def migration_version
16
+ if Rails::VERSION::MAJOR >= 5
17
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ActiveRecord
4
+ module Generators
5
+ class DeviseInvitableUpgradeGenerator < ActiveRecord::Generators::Base
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+ class_option :invite_keys, type: :array, default: ['email']
9
+
10
+ def copy_upgrade_migration
11
+ migration_template 'upgrade_devise_invitations.rb',
12
+ "db/migrate/upgrade_#{table_name}_devise_invitations.rb",
13
+ migration_version: migration_version,
14
+ invite_keys: options[:invite_keys]
15
+ end
16
+
17
+ def migration_version
18
+ if Rails::VERSION::MAJOR >= 5
19
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
20
+ end
21
+ end
22
+
23
+ def invite_keys
24
+ options[:invite_keys]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ class CreateDeviseInvitations < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :devise_invitations do |t|
4
+ t.string :invitation_token
5
+ t.datetime :invitation_created_at
6
+ t.datetime :invitation_sent_at
7
+ t.datetime :invitation_accepted_at
8
+ t.datetime :superseded_at
9
+ t.string :superseded_reason
10
+ t.datetime :revoked_at
11
+ t.datetime :resource_created_at
12
+ t.json :invite_key, null: false, default: {}
13
+ t.string :invite_key_digest, null: false
14
+ t.references :invitable, polymorphic: true
15
+ t.references :invited_by, polymorphic: true
16
+ t.timestamps
17
+ end
18
+
19
+ add_index :devise_invitations, :invitation_token, unique: true
20
+ add_index :devise_invitations, [:invitable_type, :invite_key_digest],
21
+ name: 'index_devise_invitations_on_type_and_invite_key'
22
+ end
23
+ end
@@ -0,0 +1,72 @@
1
+ require 'digest'
2
+
3
+ class Upgrade<%= table_name.camelize %>DeviseInvitations < ActiveRecord::Migration<%= migration_version %>
4
+ OLD_INVITATION_COLUMNS = %i[
5
+ invitation_token invitation_created_at invitation_sent_at
6
+ invitation_accepted_at invited_by_id invited_by_type
7
+ ].freeze
8
+
9
+ def up
10
+ create_devise_invitations_table unless table_exists?(:devise_invitations)
11
+
12
+ resource_class = Class.new(ActiveRecord::Base) do
13
+ self.table_name = '<%= table_name %>'
14
+ self.inheritance_column = :_type_disabled
15
+ end
16
+ invitation_class = Class.new(ActiveRecord::Base) do
17
+ self.table_name = 'devise_invitations'
18
+ end
19
+
20
+ resource_class.where.not(invitation_created_at: nil).find_each do |resource|
21
+ invite_key = resource.attributes.slice(<%= invite_keys.map(&:to_s).map(&:inspect).join(', ') %>)
22
+ digest_components = invite_key.map { |key, value| [key.to_s, value.to_s] }.sort.flatten
23
+ invitation_class.create!(
24
+ invitation_token: resource.invitation_token,
25
+ invitation_created_at: resource.invitation_created_at,
26
+ invitation_sent_at: resource.invitation_sent_at,
27
+ invitation_accepted_at: resource.invitation_accepted_at,
28
+ resource_created_at: resource.invitation_created_at,
29
+ invite_key: invite_key,
30
+ invite_key_digest: Digest::SHA256.hexdigest((['<%= class_name %>'] + digest_components).join("\0")),
31
+ invitable_type: '<%= class_name %>',
32
+ invitable_id: resource.id,
33
+ invited_by_type: resource.invited_by_type,
34
+ invited_by_id: resource.invited_by_id,
35
+ created_at: resource.invitation_created_at,
36
+ updated_at: resource.invitation_accepted_at || resource.invitation_sent_at || resource.invitation_created_at
37
+ )
38
+ end
39
+
40
+ OLD_INVITATION_COLUMNS.each do |column|
41
+ remove_column :<%= table_name %>, column if column_exists?(:<%= table_name %>, column)
42
+ end
43
+ end
44
+
45
+ def down
46
+ raise ActiveRecord::IrreversibleMigration,
47
+ 'Multiple invitation records cannot be losslessly moved back onto <%= table_name %>'
48
+ end
49
+
50
+ private
51
+
52
+ def create_devise_invitations_table
53
+ create_table :devise_invitations do |t|
54
+ t.string :invitation_token
55
+ t.datetime :invitation_created_at
56
+ t.datetime :invitation_sent_at
57
+ t.datetime :invitation_accepted_at
58
+ t.datetime :superseded_at
59
+ t.string :superseded_reason
60
+ t.datetime :revoked_at
61
+ t.datetime :resource_created_at
62
+ t.json :invite_key, null: false, default: {}
63
+ t.string :invite_key_digest, null: false
64
+ t.references :invitable, polymorphic: true
65
+ t.references :invited_by, polymorphic: true
66
+ t.timestamps
67
+ end
68
+ add_index :devise_invitations, :invitation_token, unique: true
69
+ add_index :devise_invitations, [:invitable_type, :invite_key_digest],
70
+ name: 'index_devise_invitations_on_type_and_invite_key'
71
+ end
72
+ end
@@ -3,14 +3,12 @@ module DeviseInvitable
3
3
  class DeviseInvitableGenerator < Rails::Generators::NamedBase
4
4
  namespace 'devise_invitable'
5
5
 
6
- desc 'Add :invitable directive in the given model. Also generate migration for ActiveRecord'
6
+ desc 'Add the :invitable directive to the given model.'
7
7
 
8
8
  def inject_devise_invitable_content
9
9
  path = File.join('app', 'models', "#{file_path}.rb")
10
10
  inject_into_file(path, 'invitable, :', after: 'devise :') if File.exist?(path)
11
11
  end
12
-
13
- hook_for :orm
14
12
  end
15
13
  end
16
- end
14
+ end
@@ -2,14 +2,16 @@ module DeviseInvitable
2
2
  module Generators
3
3
  class InstallGenerator < Rails::Generators::Base
4
4
  source_root File.expand_path('../../templates', __FILE__)
5
- desc 'Add DeviseInvitable config variables to the Devise initializer and copy DeviseInvitable locale files to your application.'
5
+ desc 'Install the invitations store, add configuration, and copy the English locale.'
6
+
7
+ hook_for :orm, as: :devise_invitable_install
6
8
 
7
9
  def add_config_options_to_initializer
8
10
  devise_initializer_path = 'config/initializers/devise.rb'
9
11
  if File.exist?(devise_initializer_path)
10
12
  old_content = File.read(devise_initializer_path)
11
13
 
12
- if old_content.match(Regexp.new(/^\s# ==> Configuration for :invitable\n/))
14
+ if old_content.match(/^\s*# ==> Configuration for :invitable\n/)
13
15
  false
14
16
  else
15
17
  inject_into_file(devise_initializer_path, before: " # ==> Configuration for :confirmable\n") do
@@ -44,14 +46,10 @@ module DeviseInvitable
44
46
  # Default: true
45
47
  # config.resend_invitation = false
46
48
 
47
- # The class name of the inviting model. If this is nil,
48
- # the #invited_by association is declared to be polymorphic.
49
- # Default: nil
50
- # config.invited_by_class_name = 'User'
51
-
52
- # The foreign key to the inviting model (if invited_by_class_name is set)
53
- # Default: :invited_by_id
54
- # config.invited_by_foreign_key = :invited_by_id
49
+ # Create a placeholder resource as soon as an invitation is sent. When false,
50
+ # the resource is created only after the invitation is accepted.
51
+ # Default: false
52
+ # config.create_resource_on_invitation = true
55
53
 
56
54
  # The column name used for counter_cache column. If this is nil,
57
55
  # the #invited_by association is declared without counter_cache.
@@ -0,0 +1,16 @@
1
+ module DeviseInvitable
2
+ module Generators
3
+ class UpgradeGenerator < Rails::Generators::NamedBase
4
+ namespace 'devise_invitable:upgrade'
5
+
6
+ class_option :invite_keys,
7
+ type: :array,
8
+ default: ['email'],
9
+ desc: 'Columns used as invite keys in the old resource table'
10
+
11
+ desc 'Generate a migration from resource-backed invitations to the invitations table.'
12
+
13
+ hook_for :orm, as: :devise_invitable_upgrade
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module Mongoid
2
+ module Generators
3
+ class DeviseInvitableInstallGenerator < Rails::Generators::Base
4
+ desc 'DeviseInvitable stores its Mongoid field definitions in the gem model.'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Mongoid
2
+ module Generators
3
+ class DeviseInvitableUpgradeGenerator < Rails::Generators::NamedBase
4
+ desc 'Print the Mongoid invitation data migration command.'
5
+
6
+ def show_migration_task
7
+ say "Run: bin/rails devise_invitable:migrate_invitations[#{class_name}]"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ namespace :devise_invitable do
2
+ desc 'Move legacy invitation fields into devise_invitations (MODEL=User also supported)'
3
+ task :migrate_invitations, [:model] => :environment do |_task, args|
4
+ model_name = args[:model] || ENV['MODEL']
5
+ raise ArgumentError, 'Pass a model name: rake devise_invitable:migrate_invitations[User]' if model_name.blank?
6
+
7
+ count = DeviseInvitable::Migrator.call(model_name.constantize)
8
+ puts "Migrated #{count} invitation#{'s' unless count == 1} for #{model_name}"
9
+ end
10
+ end
@@ -25,6 +25,7 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
25
25
  sign_out @issuer
26
26
 
27
27
  @invitee = User.where(email: invitee_email).first
28
+ invitation = @invitee.current_invitation
28
29
  assert_nil @invitee.invitation_accepted_at
29
30
  assert_not_nil @invitee.invitation_token
30
31
  assert !@invitee.confirmed?
@@ -45,10 +46,11 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
45
46
  end
46
47
 
47
48
  assert @invitee.encrypted_password.present?
48
- assert_not_nil @invitee.invitation_accepted_at
49
+ assert_nil @invitee.invitation_accepted_at
49
50
  assert_nil @invitee.invitation_token
50
- assert @invitee.invited_by_id.present?
51
- assert @invitee.invited_by_type.present?
51
+ assert_equal 'registered', invitation.reload.superseded_reason
52
+ assert_equal @issuer, invitation.invited_by
53
+ refute_predicate @invitee, :invitation_pending?
52
54
  assert @invitee.confirmation_token.present?
53
55
  assert !@invitee.confirmed?
54
56
  end
@@ -79,6 +81,25 @@ class DeviseInvitable::RegistrationsControllerTest < ActionController::TestCase
79
81
  assert !@user.confirmed?
80
82
  end
81
83
 
84
+ test "registration supersedes invitations when placeholders are disabled" do
85
+ User.create_resource_on_invitation = false
86
+ invitee_email = 'deferred@example.org'
87
+ User.invite!(email: invitee_email)
88
+ invitation = DeviseInvitable::Invitation.first
89
+
90
+ assert_nil User.where(email: invitee_email).first
91
+
92
+ post :create, params: { user: { email: invitee_email, password: '1password', bio: '.' } }
93
+
94
+ invitee = User.find_by!(email: invitee_email)
95
+ assert_equal invitee, invitation.reload.invitable
96
+ assert_equal 'registered', invitation.superseded_reason
97
+ assert_nil invitation.invitation_accepted_at
98
+ refute_predicate invitee, :invitation_pending?
99
+ ensure
100
+ User.create_resource_on_invitation = true
101
+ end
102
+
82
103
  test "not invitable resources can register" do
83
104
  @request.env['devise.mapping'] = Devise.mappings[:admin]
84
105
  invitee_email = 'invitee@example.org'
@@ -6,30 +6,37 @@ require 'generators/devise_invitable/devise_invitable_generator'
6
6
  class GeneratorsTest < ActiveSupport::TestCase
7
7
  RAILS_APP_PATH = File.expand_path("../rails_app", __FILE__)
8
8
 
9
- test "rails g should include the 6 generators" do
9
+ test "rails g should include the generators" do
10
10
  @output = `cd #{RAILS_APP_PATH} && rails g`
11
- generators = %w/devise_invitable devise_invitable:form_for devise_invitable:install devise_invitable:invitation_views devise_invitable:simple_form_for devise_invitable:views/
11
+ generators = %w/devise_invitable devise_invitable:form_for devise_invitable:install devise_invitable:invitation_views devise_invitable:simple_form_for devise_invitable:upgrade devise_invitable:views/
12
12
 
13
13
  generators.each do |generator|
14
- @output.include? generator
14
+ assert_includes @output, generator
15
15
  end
16
16
  end
17
17
 
18
18
  test "rails g devise_invitable:install" do
19
19
  @output = `cd #{RAILS_APP_PATH} && rails g devise_invitable:install -p`
20
- puts @output
21
- assert @output.match(%r{(inject|insert|File unchanged! The supplied flag value not found!).* config/initializers/devise\.rb\n})
22
20
  assert @output.match(%r|create.* config/locales/devise_invitable\.en\.yml\n|)
21
+ if DEVISE_ORM == :active_record
22
+ assert_match(%r|create.*db/migrate/\d{14}_create_devise_invitations\.rb|, @output)
23
+ end
23
24
  end
24
25
 
25
26
  test "rails g devise_invitable Octopussy" do
26
27
  @output = `cd #{RAILS_APP_PATH} && rails g devise_invitable Octopussy -p`
27
28
  assert @output.match(%r{(inject|insert|File unchanged! The supplied flag value not found!).* app/models/octopussy\.rb\n})
28
- assert @output.match(%r|invoke.* #{DEVISE_ORM}\n|)
29
+ assert_no_match(%r|invoke.* #{DEVISE_ORM}\n|, @output)
30
+ assert_no_match(%r|db/migrate/\d{14}_devise_invitable_add_to_octopussies\.rb|, @output)
31
+ end
32
+
33
+
34
+ test "rails g devise_invitable:upgrade User" do
35
+ @output = `cd #{RAILS_APP_PATH} && rails g devise_invitable:upgrade User -p`
29
36
  if DEVISE_ORM == :active_record
30
- assert @output.match(%r|create.* db/migrate/\d{14}_devise_invitable_add_to_octopussies\.rb\n|)
31
- elsif DEVISE_ORM == :mongoid
32
- assert !@output.match(%r|create.* db/migrate/\d{14}_devise_invitable_add_to_octopussies\.rb\n|)
37
+ assert_match(%r|create.*db/migrate/\d{14}_upgrade_users_devise_invitations\.rb|, @output)
38
+ else
39
+ assert_match(/devise_invitable:migrate_invitations\[User\]/, @output)
33
40
  end
34
41
  end
35
42
  end
@@ -152,7 +152,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
152
152
 
153
153
  visit new_user_password_path
154
154
  fill_in 'user_email', with: 'valid@email.com'
155
- click_button 'Send me reset password instructions'
155
+ find('input[type="submit"]').click
156
156
 
157
157
  visit edit_user_password_path(reset_password_token: Thread.current[:token])
158
158
  set_password visit: false, button: 'Change my password'
@@ -209,7 +209,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
209
209
  assert_equal 2, user.invitation_limit
210
210
  end
211
211
 
212
- test 'should not decrement invitation limit when trying to invite again a user which is invited' do
212
+ test 'should decrement invitation limit for each invitation record' do
213
213
  User.stubs(:invitation_limit).returns(3)
214
214
 
215
215
  user = create_full_user
@@ -227,7 +227,7 @@ class InvitationTest < ActionDispatch::IntegrationTest
227
227
  assert_equal root_path, current_path
228
228
  assert page.has_css?('p#notice', text: 'An invitation email has been sent to user@test.com.')
229
229
  user = User.find(user.id)
230
- assert_equal 2, user.invitation_limit
230
+ assert_equal 1, user.invitation_limit
231
231
  end
232
232
 
233
233
  test 'invited_by should be set when user invites someone' do
@@ -101,6 +101,7 @@ class InvitationMailTest < ActionMailer::TestCase
101
101
 
102
102
  def initialize(*args); end
103
103
  def deliver; end
104
+ def deliver_now; end
104
105
  end
105
106
  Devise.mailer = 'InvitationMailTest::CustomMailer'
106
107
 
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+ require 'open3'
3
+
4
+ class ParentMailerTest < ActiveSupport::TestCase
5
+ APPLICATION = File.expand_path('../rails_app/config/application', __dir__)
6
+
7
+ # Each boot runs in a fresh process: the failure only shows up when nothing has loaded
8
+ # ActionMailer::Base yet, which is never the case inside this test process.
9
+ def boot_with_app_parent_mailer(first_reference)
10
+ script = <<~RUBY
11
+ require #{APPLICATION.inspect}
12
+ Devise.parent_mailer = 'ApplicationMailer'
13
+ RailsApp::Application.initialize!
14
+ #{first_reference}
15
+ print "invitation_instructions=\#{Devise.mailer.method_defined?(:invitation_instructions)}"
16
+ RUBY
17
+ Open3.capture3({ 'RAILS_ENV' => 'test' }, RbConfig.ruby, '-e', script)
18
+ end
19
+
20
+ test 'Devise.mailer resolves when the parent mailer is an app mailer' do
21
+ stdout, stderr, status = boot_with_app_parent_mailer('Devise.mailer')
22
+ assert status.success?, stderr
23
+ assert_match(/invitation_instructions=true\z/, stdout)
24
+ end
25
+
26
+ test 'the parent mailer can be the first mailer loaded, as with eager loading' do
27
+ stdout, stderr, status = boot_with_app_parent_mailer('ApplicationMailer')
28
+ assert status.success?, stderr
29
+ assert_match(/invitation_instructions=true\z/, stdout)
30
+ end
31
+ end