rodauth-rails 1.2.2 → 1.4.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +108 -2
  4. data/lib/generators/rodauth/install_generator.rb +9 -10
  5. data/lib/generators/rodauth/migration/{account_expiration.erb → active_record/account_expiration.erb} +0 -0
  6. data/lib/generators/rodauth/migration/{active_sessions.erb → active_record/active_sessions.erb} +0 -0
  7. data/lib/generators/rodauth/migration/{audit_logging.erb → active_record/audit_logging.erb} +0 -0
  8. data/lib/generators/rodauth/migration/{base.erb → active_record/base.erb} +2 -7
  9. data/lib/generators/rodauth/migration/{disallow_password_reuse.erb → active_record/disallow_password_reuse.erb} +1 -1
  10. data/lib/generators/rodauth/migration/{email_auth.erb → active_record/email_auth.erb} +0 -0
  11. data/lib/generators/rodauth/migration/{jwt_refresh.erb → active_record/jwt_refresh.erb} +0 -0
  12. data/lib/generators/rodauth/migration/{lockout.erb → active_record/lockout.erb} +0 -0
  13. data/lib/generators/rodauth/migration/{otp.erb → active_record/otp.erb} +0 -0
  14. data/lib/generators/rodauth/migration/{password_expiration.erb → active_record/password_expiration.erb} +0 -0
  15. data/lib/generators/rodauth/migration/{recovery_codes.erb → active_record/recovery_codes.erb} +0 -0
  16. data/lib/generators/rodauth/migration/{remember.erb → active_record/remember.erb} +0 -0
  17. data/lib/generators/rodauth/migration/{reset_password.erb → active_record/reset_password.erb} +0 -0
  18. data/lib/generators/rodauth/migration/{single_session.erb → active_record/single_session.erb} +0 -0
  19. data/lib/generators/rodauth/migration/{sms_codes.erb → active_record/sms_codes.erb} +0 -0
  20. data/lib/generators/rodauth/migration/{verify_account.erb → active_record/verify_account.erb} +0 -0
  21. data/lib/generators/rodauth/migration/{verify_login_change.erb → active_record/verify_login_change.erb} +0 -0
  22. data/lib/generators/rodauth/migration/{webauthn.erb → active_record/webauthn.erb} +0 -0
  23. data/lib/generators/rodauth/migration/sequel/account_expiration.erb +7 -0
  24. data/lib/generators/rodauth/migration/sequel/active_sessions.erb +8 -0
  25. data/lib/generators/rodauth/migration/sequel/audit_logging.erb +17 -0
  26. data/lib/generators/rodauth/migration/sequel/base.erb +25 -0
  27. data/lib/generators/rodauth/migration/sequel/disallow_password_reuse.erb +6 -0
  28. data/lib/generators/rodauth/migration/sequel/email_auth.erb +7 -0
  29. data/lib/generators/rodauth/migration/sequel/jwt_refresh.erb +8 -0
  30. data/lib/generators/rodauth/migration/sequel/lockout.erb +11 -0
  31. data/lib/generators/rodauth/migration/sequel/otp.erb +7 -0
  32. data/lib/generators/rodauth/migration/sequel/password_expiration.erb +5 -0
  33. data/lib/generators/rodauth/migration/sequel/recovery_codes.erb +6 -0
  34. data/lib/generators/rodauth/migration/sequel/remember.erb +6 -0
  35. data/lib/generators/rodauth/migration/sequel/reset_password.erb +7 -0
  36. data/lib/generators/rodauth/migration/sequel/single_session.erb +5 -0
  37. data/lib/generators/rodauth/migration/sequel/sms_codes.erb +8 -0
  38. data/lib/generators/rodauth/migration/sequel/verify_account.erb +7 -0
  39. data/lib/generators/rodauth/migration/sequel/verify_login_change.erb +7 -0
  40. data/lib/generators/rodauth/migration/sequel/webauthn.erb +13 -0
  41. data/lib/generators/rodauth/migration_generator.rb +89 -9
  42. data/lib/generators/rodauth/templates/app/mailers/rodauth_mailer.rb +24 -0
  43. data/lib/generators/rodauth/templates/app/misc/rodauth_main.rb +4 -1
  44. data/lib/generators/rodauth/templates/app/models/account.rb +11 -0
  45. data/lib/generators/rodauth/templates/db/migrate/create_rodauth.rb +8 -0
  46. data/lib/rodauth/rails/app.rb +19 -7
  47. data/lib/rodauth/rails/controller_methods.rb +9 -0
  48. data/lib/rodauth/rails/feature/associations.rb +54 -0
  49. data/lib/rodauth/rails/feature/base.rb +10 -0
  50. data/lib/rodauth/rails/feature/instrumentation.rb +8 -0
  51. data/lib/rodauth/rails/feature.rb +2 -0
  52. data/lib/rodauth/rails/middleware.rb +9 -0
  53. data/lib/rodauth/rails/model.rb +8 -8
  54. data/lib/rodauth/rails/railtie.rb +9 -0
  55. data/lib/rodauth/rails/test/controller.rb +41 -0
  56. data/lib/rodauth/rails/test.rb +7 -0
  57. data/lib/rodauth/rails/version.rb +1 -1
  58. data/rodauth-rails.gemspec +2 -1
  59. metadata +57 -26
  60. data/lib/generators/rodauth/migration_helpers.rb +0 -77
  61. data/lib/rodauth/rails/app/flash.rb +0 -45
  62. data/lib/rodauth/rails/app/middleware.rb +0 -36
  63. data/lib/rodauth/rails/model/associations.rb +0 -195
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 402cbf2f62d93eae97353a3aed436ce742b41421e880176649a7271c5516b39c
4
- data.tar.gz: ca035e7a60c54b4e1b6f2a42ea6405f43811146141c30c5a6d14fd7c0600669e
3
+ metadata.gz: 8b5fb22e3d8c84eafedffa3463b4e0ecdf481189b8b48adc2d31005f86251d87
4
+ data.tar.gz: 142e229b7c9a9b078f773f99885117268e759df2eb49e5252ce21754dcf61763
5
5
  SHA512:
6
- metadata.gz: 888fd37f380d6f4896b544374c6681445dbb0e90d692dd7c0239aa043c9d6c0cb2aaafa9b47f271cd6cc70ad3a6c88693c988901aed1e5bc0f77ed5c92cb4ab1
7
- data.tar.gz: 48fe23bfbd3d78c3378ab47ecf92ffd7f87fe768f996764a71b8534a2cab8731ebc080998b03ea9a78f1fecd949548782ca8e38dc17c56fa2606ea11f7a7fbe9
6
+ metadata.gz: b1c30c5b1714a80466ad3775720be50d2f02b8d06d016638e6d1ebfb7515cd6060463b975f9810977fe74eb7330ce12b9c6ff81839e41b2e4044615c606ca7bb
7
+ data.tar.gz: 4a532058b27cfc07d2ed234457b880a609c7b35186db976a547be2e83d1c18df7ac72402a9d52155819f2d97edb63b0e7742e908b6cb3ed1b40c2deae18e7e2e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 1.4.0 (2022-05-04)
2
+
3
+ * Move association definitions to `#associations` Rodauth method, allowing external features to extend them (@janko)
4
+
5
+ * Add Sequel support for generating database migrations, model, and mailer (@janko)
6
+
7
+ * Skip calling Rodauth app on asset requests when using Sprockets or Propshaft (@janko)
8
+
9
+ ## 1.3.1 (2022-04-22)
10
+
11
+ * Ensure response status is logged when calling a halting rodauth method inside a controller (@janko)
12
+
13
+ ## 1.3.0 (2022-04-01)
14
+
15
+ * Store password hash on the `accounts` table in generated Rodauth migration and configuration (@janko)
16
+
17
+ * Add support for controller testing with Minitest or RSpec (@janko)
18
+
19
+ * Fix `enum` declaration in generated `Account` model for Active Record < 7.0 (@janko)
20
+
21
+ * Ensure `require_login_redirect` points to the login page even if the login route changes (@janko)
22
+
1
23
  ## 1.2.2 (2022-02-22)
2
24
 
3
25
  * Fix flash messages not being preserved through consecutive redirects (@janko)
data/README.md CHANGED
@@ -10,7 +10,6 @@ Provides Rails integration for the [Rodauth] authentication framework.
10
10
  * [Rails demo](https://github.com/janko/rodauth-demo-rails)
11
11
  * [JSON API guide](https://github.com/janko/rodauth-rails/wiki/JSON-API)
12
12
  * [OmniAuth guide](https://github.com/janko/rodauth-rails/wiki/OmniAuth)
13
- * [Testing guide](https://github.com/janko/rodauth-rails/wiki/Testing)
14
13
 
15
14
  🎥 Screencasts:
16
15
 
@@ -40,7 +39,7 @@ of the advantages that stand out for me:
40
39
  * consistent before/after hooks around everything
41
40
  * dedicated object encapsulating all authentication logic
42
41
 
43
- One commmon concern is the fact that, unlike most other authentication
42
+ One common concern is the fact that, unlike most other authentication
44
43
  frameworks for Rails, Rodauth uses [Sequel] for database interaction instead of
45
44
  Active Record. There are good reasons for this, and to make Rodauth work
46
45
  smoothly alongside Active Record, rodauth-rails configures Sequel to [reuse
@@ -615,6 +614,33 @@ Rodauth::Rails.model(association_options: -> (name) {
615
614
  })
616
615
  ```
617
616
 
617
+ #### Extending Associations
618
+
619
+ External features can extend the list of associations with their own
620
+ definitions, which the model mixin will pick up and declare the new associations
621
+ on the model.
622
+
623
+ ```rb
624
+ # lib/rodauth/features/foo.rb
625
+ module Rodauth
626
+ Feature.define(:foo, :Foo) do
627
+ auth_value_method :foo_table, :account_foos
628
+ auth_value_method :foo_id_column, :id
629
+
630
+ def associations
631
+ list = super
632
+ list << {
633
+ name: :foo, # will define `Account::Foo` model
634
+ type: :one, # or :many
635
+ table: foo_table,
636
+ foreign_key: foo_id_column
637
+ }
638
+ list
639
+ end
640
+ end
641
+ end
642
+ ```
643
+
618
644
  ## Multiple configurations
619
645
 
620
646
  If you need to handle multiple types of accounts that require different
@@ -783,6 +809,86 @@ Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })
783
809
  Rodauth::Rails.rodauth(:admin, params: { "param" => "value" })
784
810
  ```
785
811
 
812
+ ## Testing
813
+
814
+ For system and integration tests, which run the whole middleware stack,
815
+ authentication can be exercised normally via HTTP endpoints. See [this wiki
816
+ page](https://github.com/janko/rodauth-rails/wiki/Testing) for some examples.
817
+
818
+ For controller tests, you can log in accounts by modifying the session:
819
+
820
+ ```rb
821
+ # app/controllers/articles_controller.rb
822
+ class ArticlesController < ApplicationController
823
+ before_action -> { rodauth.require_authentication }
824
+
825
+ def index
826
+ # ...
827
+ end
828
+ end
829
+ ```
830
+ ```rb
831
+ # test/controllers/articles_controller_test.rb
832
+ class ArticlesControllerTest < ActionController::TestCase
833
+ test "required authentication" do
834
+ get :index
835
+
836
+ assert_response 302
837
+ assert_redirected_to "/login"
838
+ assert_equal "Please login to continue", flash[:alert]
839
+
840
+ account = Account.create!(email: "user@example.com", password: "secret", status: "verified")
841
+ login(account)
842
+
843
+ get :index
844
+ assert_response 200
845
+
846
+ logout
847
+
848
+ get :index
849
+ assert_response 302
850
+ assert_equal "Please login to continue", flash[:alert]
851
+ end
852
+
853
+ private
854
+
855
+ # Manually modify the session into what Rodauth expects.
856
+ def login(account)
857
+ session[:account_id] = account.id
858
+ session[:authenticated_by] = ["password"] # or ["password", "totp"] for MFA
859
+ end
860
+
861
+ def logout
862
+ session.clear
863
+ end
864
+ end
865
+ ```
866
+
867
+ If you're using multiple configurations with different session prefixes, you'll need
868
+ to make sure to use those in controller tests as well:
869
+
870
+ ```rb
871
+ class RodauthAdmin < Rodauth::Rails::Auth
872
+ configure do
873
+ session_key_prefix "admin_"
874
+ end
875
+ end
876
+ ```
877
+ ```rb
878
+ # in a controller test:
879
+ session[:admin_account_id] = account.id
880
+ session[:admin_authenticated_by] = ["password"]
881
+ ```
882
+
883
+ If you want to access the Rodauth instance in controller tests, you can do so
884
+ through the controller instance:
885
+
886
+ ```rb
887
+ # in a controller test:
888
+ @controller.rodauth #=> #<RodauthMain ...>
889
+ @controller.rodauth(:admin) #=> #<RodauthAdmin ...>
890
+ ```
891
+
786
892
  ## Configuring
787
893
 
788
894
  ### Configuration methods
@@ -1,15 +1,10 @@
1
1
  require "rails/generators/base"
2
- require "rails/generators/active_record/migration"
3
- require "generators/rodauth/migration_helpers"
4
2
  require "securerandom"
5
3
 
6
4
  module Rodauth
7
5
  module Rails
8
6
  module Generators
9
7
  class InstallGenerator < ::Rails::Generators::Base
10
- include ::ActiveRecord::Generators::Migration
11
- include MigrationHelpers
12
-
13
8
  if RUBY_ENGINE == "jruby"
14
9
  SEQUEL_ADAPTERS = {
15
10
  "sqlite3" => "sqlite",
@@ -40,9 +35,7 @@ module Rodauth
40
35
  class_option :jwt, type: :boolean, desc: "Configure JWT support"
41
36
 
42
37
  def create_rodauth_migration
43
- return unless defined?(ActiveRecord::Railtie)
44
-
45
- migration_template "db/migrate/create_rodauth.rb"
38
+ invoke "rodauth:migration", migration_features, name: "create_rodauth"
46
39
  end
47
40
 
48
41
  def create_rodauth_initializer
@@ -66,8 +59,6 @@ module Rodauth
66
59
  end
67
60
 
68
61
  def create_account_model
69
- return unless defined?(ActiveRecord::Railtie)
70
-
71
62
  template "app/models/account.rb"
72
63
  end
73
64
 
@@ -112,6 +103,14 @@ module Rodauth
112
103
  scheme = "jdbc:#{scheme}" if RUBY_ENGINE == "jruby"
113
104
  scheme
114
105
  end
106
+
107
+ def activerecord_adapter
108
+ if ActiveRecord::Base.respond_to?(:connection_db_config)
109
+ ActiveRecord::Base.connection_db_config.adapter
110
+ else
111
+ ActiveRecord::Base.connection_config.fetch(:adapter)
112
+ end
113
+ end
115
114
  end
116
115
  end
117
116
  end
@@ -3,23 +3,18 @@ enable_extension "citext"
3
3
 
4
4
  <% end -%>
5
5
  create_table :accounts<%= primary_key_type %> do |t|
6
+ t.integer :status, null: false, default: 1
6
7
  <% case activerecord_adapter -%>
7
8
  <% when "postgresql" -%>
8
9
  t.citext :email, null: false
9
10
  <% else -%>
10
11
  t.string :email, null: false
11
12
  <% end -%>
12
- t.integer :status, null: false, default: 1
13
13
  <% case activerecord_adapter -%>
14
14
  <% when "postgresql", "sqlite3" -%>
15
15
  t.index :email, unique: true, where: "status IN (1, 2)"
16
16
  <% else -%>
17
17
  t.index :email, unique: true
18
18
  <% end -%>
19
- end
20
-
21
- # Used if storing password hashes in a separate table (default)
22
- create_table :account_password_hashes<%= primary_key_type %> do |t|
23
- t.foreign_key :accounts, column: :id
24
- t.string :password_hash, null: false
19
+ t.string :password_hash
25
20
  end
@@ -1,4 +1,4 @@
1
- # Used by the disallow_password_reuse feature
1
+ # Used by the disallow password reuse feature
2
2
  create_table :account_previous_password_hashes do |t|
3
3
  t.references :account, foreign_key: true<%= primary_key_type(:type) %>
4
4
  t.string :password_hash, null: false
@@ -0,0 +1,7 @@
1
+ # Used by the account expiration feature
2
+ create_table :account_activity_times do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ DateTime :last_activity_at, null: false
5
+ DateTime :last_login_at, null: false
6
+ DateTime :expired_at
7
+ end
@@ -0,0 +1,8 @@
1
+ # Used by the active sessions feature
2
+ create_table :account_active_session_keys do
3
+ foreign_key :account_id, :accounts, type: :Bignum
4
+ String :session_id
5
+ Time :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
6
+ Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ primary_key [:account_id, :session_id]
8
+ end
@@ -0,0 +1,17 @@
1
+ # Used by the audit logging feature
2
+ create_table :account_authentication_audit_logs do
3
+ primary_key :id, type: :Bignum
4
+ foreign_key :account_id, :accounts, null: false, type: :Bignum
5
+ DateTime :at, null: false, default: Sequel::CURRENT_TIMESTAMP
6
+ String :message, null: false
7
+ <% case db.database_type -%>
8
+ <% when :postgres -%>
9
+ jsonb :metadata
10
+ <% when :sqlite, :mysql -%>
11
+ json :metadata
12
+ <% else -%>
13
+ String :metadata
14
+ <% end -%>
15
+ index [:account_id, :at], name: :audit_account_at_idx
16
+ index :at, name: :audit_at_idx
17
+ end
@@ -0,0 +1,25 @@
1
+ <% if db.database_type == :postgres -%>
2
+ enable_extension "citext"
3
+
4
+ <% end -%>
5
+ create_table :accounts do
6
+ primary_key :id, type: :Bignum
7
+ <% if db.database_type == :postgres -%>
8
+ citext :email, null: false
9
+ constraint :valid_email, email: /^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/
10
+ <% else -%>
11
+ String :email, null: false
12
+ <% end -%>
13
+ String :status, null: false, default: "unverified"
14
+ <% if db.supports_partial_indexes? -%>
15
+ index :email, unique: true, where: { status: ["unverified", "verified"] }
16
+ <% else -%>
17
+ index :email, unique: true
18
+ <% end -%>
19
+ end
20
+
21
+ # Used if storing password hashes in a separate table (default)
22
+ create_table :account_password_hashes do
23
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
24
+ String :password_hash, null: false
25
+ end
@@ -0,0 +1,6 @@
1
+ # Used by the disallow password reuse feature
2
+ create_table :account_previous_password_hashes do
3
+ primary_key :id, type: :Bignum
4
+ foreign_key :account_id, :accounts, type: :Bignum
5
+ String :password_hash, null: false
6
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the email auth feature
2
+ create_table :account_email_auth_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ DateTime :deadline, null: false
6
+ DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ end
@@ -0,0 +1,8 @@
1
+ # Used by the jwt refresh feature
2
+ create_table :account_jwt_refresh_keys do
3
+ primary_key :id, type: :Bignum
4
+ foreign_key :account_id, :accounts, null: false, type: :Bignum
5
+ String :key, null: false
6
+ DateTime :deadline, null: false
7
+ index :account_id, name: :account_jwt_rk_account_id_idx
8
+ end
@@ -0,0 +1,11 @@
1
+ # Used by the lockout feature
2
+ create_table :account_login_failures do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ Integer :number, null: false, default: 1
5
+ end
6
+ create_table :account_lockouts do
7
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
8
+ String :key, null: false
9
+ DateTime :deadline, null: false
10
+ DateTime :email_last_sent
11
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the otp feature
2
+ create_table :account_otp_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ Integer :num_failures, null: false, default: 0
6
+ Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ end
@@ -0,0 +1,5 @@
1
+ # Used by the password expiration feature
2
+ create_table :account_password_change_times do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ DateTime :changed_at, null: false, default: Sequel::CURRENT_TIMESTAMP
5
+ end
@@ -0,0 +1,6 @@
1
+ # Used by the recovery codes feature
2
+ create_table :account_recovery_codes do
3
+ foreign_key :id, :accounts, type: :Bignum
4
+ String :code
5
+ primary_key [:id, :code]
6
+ end
@@ -0,0 +1,6 @@
1
+ # Used by the remember me feature
2
+ create_table :account_remember_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ DateTime :deadline, null: false
6
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the password reset feature
2
+ create_table :account_password_reset_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ DateTime :deadline, null: false
6
+ DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ end
@@ -0,0 +1,5 @@
1
+ # Used by the single session feature
2
+ create_table :account_session_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ end
@@ -0,0 +1,8 @@
1
+ # Used by the sms codes feature
2
+ create_table :account_sms_codes do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :phone_number, null: false
5
+ Integer :num_failures
6
+ String :code
7
+ DateTime :code_issued_at, null: false, default: Sequel::CURRENT_TIMESTAMP
8
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the account verification feature
2
+ create_table :account_verification_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ DateTime :requested_at, null: false, default: Sequel::CURRENT_TIMESTAMP
6
+ DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
7
+ end
@@ -0,0 +1,7 @@
1
+ # Used by the verify login change feature
2
+ create_table :account_login_change_keys do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :key, null: false
5
+ String :login, null: false
6
+ DateTime :deadline, null: false
7
+ end
@@ -0,0 +1,13 @@
1
+ # Used by the webauthn feature
2
+ create_table :account_webauthn_user_ids do
3
+ foreign_key :id, :accounts, primary_key: true, type: :Bignum
4
+ String :webauthn_id, null: false
5
+ end
6
+ create_table :account_webauthn_keys do
7
+ foreign_key :account_id, :accounts, type: :Bignum
8
+ String :webauthn_id
9
+ String :public_key, null: false
10
+ Integer :sign_count, null: false
11
+ Time :last_use, null: false, default: Sequel::CURRENT_TIMESTAMP
12
+ primary_key [:account_id, :webauthn_id]
13
+ end
@@ -1,14 +1,11 @@
1
1
  require "rails/generators/base"
2
2
  require "rails/generators/active_record/migration"
3
- require "generators/rodauth/migration_helpers"
3
+ require "erb"
4
4
 
5
5
  module Rodauth
6
6
  module Rails
7
7
  module Generators
8
8
  class MigrationGenerator < ::Rails::Generators::Base
9
- include ::ActiveRecord::Generators::Migration
10
- include MigrationHelpers
11
-
12
9
  source_root "#{__dir__}/templates"
13
10
  namespace "rodauth:migration"
14
11
 
@@ -20,19 +17,102 @@ module Rodauth
20
17
  desc: "Name of the generated migration file"
21
18
 
22
19
  def create_rodauth_migration
23
- return unless defined?(ActiveRecord::Railtie)
24
20
  return if features.empty?
25
21
 
26
- migration_template "db/migrate/create_rodauth.rb", "#{migration_name}.rb"
22
+ migration_template "db/migrate/create_rodauth.rb", File.join(db_migrate_path, "#{migration_name}.rb")
27
23
  end
28
24
 
29
- def migration_features
30
- features
31
- end
25
+ private
32
26
 
33
27
  def migration_name
34
28
  options[:name] || "create_rodauth_#{features.join("_")}"
35
29
  end
30
+
31
+ def migration_content
32
+ features
33
+ .select { |feature| File.exist?(migration_chunk(feature)) }
34
+ .map { |feature| File.read(migration_chunk(feature)) }
35
+ .map { |content| erb_eval(content) }
36
+ .join("\n")
37
+ .indent(4)
38
+ end
39
+
40
+ def erb_eval(content)
41
+ if ERB.version[/\d+\.\d+\.\d+/].to_s >= "2.2.0"
42
+ ERB.new(content, trim_mode: "-").result(binding)
43
+ else
44
+ ERB.new(content, 0, "-").result(binding)
45
+ end
46
+ end
47
+
48
+ if defined?(::ActiveRecord::Railtie) # Active Record
49
+ include ::ActiveRecord::Generators::Migration
50
+
51
+ def db_migrate_path
52
+ return "db/migrate" unless ActiveRecord.version >= Gem::Version.new("5.0")
53
+
54
+ super
55
+ end
56
+
57
+ def migration_chunk(feature)
58
+ "#{__dir__}/migration/active_record/#{feature}.erb"
59
+ end
60
+
61
+ def migration_version
62
+ return unless ActiveRecord.version >= Gem::Version.new("5.0")
63
+
64
+ "[#{ActiveRecord::Migration.current_version}]"
65
+ end
66
+
67
+ def activerecord_adapter
68
+ if ActiveRecord::Base.respond_to?(:connection_db_config)
69
+ ActiveRecord::Base.connection_db_config.adapter
70
+ else
71
+ ActiveRecord::Base.connection_config.fetch(:adapter)
72
+ end
73
+ end
74
+
75
+ def primary_key_type(key = :id)
76
+ generators = ::Rails.application.config.generators
77
+ column_type = generators.options[:active_record][:primary_key_type]
78
+
79
+ return unless column_type
80
+
81
+ if key
82
+ ", #{key}: :#{column_type}"
83
+ else
84
+ column_type
85
+ end
86
+ end
87
+
88
+ def current_timestamp
89
+ if ActiveRecord.version >= Gem::Version.new("5.0")
90
+ %(-> { "CURRENT_TIMESTAMP" })
91
+ else
92
+ %(OpenStruct.new(quoted_id: "CURRENT_TIMESTAMP"))
93
+ end
94
+ end
95
+ else # Sequel
96
+ include ::Rails::Generators::Migration
97
+
98
+ def self.next_migration_number(dirname)
99
+ next_migration_number = current_migration_number(dirname) + 1
100
+ [Time.now.utc.strftime('%Y%m%d%H%M%S'), format('%.14d', next_migration_number)].max
101
+ end
102
+
103
+ def db_migrate_path
104
+ "db/migrate"
105
+ end
106
+
107
+ def migration_chunk(feature)
108
+ "#{__dir__}/migration/sequel/#{feature}.erb"
109
+ end
110
+
111
+ def db
112
+ db = ::Sequel::DATABASES.first if defined?(::Sequel)
113
+ db or fail Rodauth::Rails::Error, "missing Sequel database connection"
114
+ end
115
+ end
36
116
  end
37
117
  end
38
118
  end