pollett 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +81 -0
  4. data/Rakefile +25 -0
  5. data/app/controllers/concerns/pollett/controller.rb +37 -0
  6. data/app/controllers/pollett/application_controller.rb +4 -0
  7. data/app/controllers/pollett/keys_controller.rb +5 -0
  8. data/app/controllers/pollett/sessions_controller.rb +5 -0
  9. data/app/controllers/pollett/users_controller.rb +5 -0
  10. data/app/mailers/pollett/mailer.rb +5 -0
  11. data/app/models/concerns/pollett/user.rb +40 -0
  12. data/app/models/pollett/context.rb +5 -0
  13. data/app/models/pollett/key.rb +5 -0
  14. data/app/models/pollett/session.rb +5 -0
  15. data/app/serializers/pollett/key_serializer.rb +5 -0
  16. data/app/serializers/pollett/session_serializer.rb +5 -0
  17. data/app/serializers/user_serializer.rb +3 -0
  18. data/app/services/pollett/authenticate_user.rb +5 -0
  19. data/app/services/pollett/change_password.rb +5 -0
  20. data/app/services/pollett/create_session.rb +5 -0
  21. data/app/services/pollett/register_user.rb +5 -0
  22. data/app/services/pollett/reset_password.rb +5 -0
  23. data/app/views/pollett/mailer/reset.text.erb +5 -0
  24. data/app/views/pollett/mailer/welcome.text.erb +1 -0
  25. data/config/locales/en.yml +14 -0
  26. data/config/routes.rb +11 -0
  27. data/db/migrate/20150226024506_create_pollett_contexts.rb +21 -0
  28. data/lib/generators/pollett/install/install_generator.rb +123 -0
  29. data/lib/generators/pollett/install/templates/db/migrate/add_pollett_to_users.rb +21 -0
  30. data/lib/generators/pollett/install/templates/db/migrate/create_users.rb +15 -0
  31. data/lib/generators/pollett/install/templates/initializer.rb +3 -0
  32. data/lib/generators/pollett/install/templates/user.rb +3 -0
  33. data/lib/pollett.rb +20 -0
  34. data/lib/pollett/concerns.rb +5 -0
  35. data/lib/pollett/concerns/controllers.rb +3 -0
  36. data/lib/pollett/concerns/controllers/keys_controller.rb +37 -0
  37. data/lib/pollett/concerns/controllers/sessions_controller.rb +43 -0
  38. data/lib/pollett/concerns/controllers/users_controller.rb +28 -0
  39. data/lib/pollett/concerns/mailers.rb +1 -0
  40. data/lib/pollett/concerns/mailers/mailer.rb +24 -0
  41. data/lib/pollett/concerns/models.rb +3 -0
  42. data/lib/pollett/concerns/models/context.rb +42 -0
  43. data/lib/pollett/concerns/models/key.rb +13 -0
  44. data/lib/pollett/concerns/models/session.rb +20 -0
  45. data/lib/pollett/concerns/serializers.rb +2 -0
  46. data/lib/pollett/concerns/serializers/context_serializer.rb +21 -0
  47. data/lib/pollett/concerns/serializers/user_serializer.rb +16 -0
  48. data/lib/pollett/concerns/services.rb +5 -0
  49. data/lib/pollett/concerns/services/authenticate_user.rb +21 -0
  50. data/lib/pollett/concerns/services/change_password.rb +21 -0
  51. data/lib/pollett/concerns/services/create_session.rb +27 -0
  52. data/lib/pollett/concerns/services/register_user.rb +29 -0
  53. data/lib/pollett/concerns/services/reset_password.rb +27 -0
  54. data/lib/pollett/configuration.rb +34 -0
  55. data/lib/pollett/engine.rb +12 -0
  56. data/lib/pollett/rspec.rb +6 -0
  57. data/lib/pollett/testing/request_helper.rb +106 -0
  58. data/lib/pollett/version.rb +3 -0
  59. data/spec/dummy/README.rdoc +28 -0
  60. data/spec/dummy/Rakefile +6 -0
  61. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  62. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  63. data/spec/dummy/app/controllers/application_controller.rb +12 -0
  64. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  65. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  66. data/spec/dummy/app/models/user.rb +3 -0
  67. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  68. data/spec/dummy/app/views/layouts/mailer.text.erb +6 -0
  69. data/spec/dummy/bin/bundle +3 -0
  70. data/spec/dummy/bin/rails +4 -0
  71. data/spec/dummy/bin/rake +4 -0
  72. data/spec/dummy/bin/setup +29 -0
  73. data/spec/dummy/config.ru +4 -0
  74. data/spec/dummy/config/application.rb +32 -0
  75. data/spec/dummy/config/boot.rb +5 -0
  76. data/spec/dummy/config/database.yml +85 -0
  77. data/spec/dummy/config/environment.rb +5 -0
  78. data/spec/dummy/config/environments/development.rb +41 -0
  79. data/spec/dummy/config/environments/production.rb +79 -0
  80. data/spec/dummy/config/environments/test.rb +42 -0
  81. data/spec/dummy/config/initializers/active_model_serializers.rb +1 -0
  82. data/spec/dummy/config/initializers/assets.rb +11 -0
  83. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  84. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  85. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  86. data/spec/dummy/config/initializers/inflections.rb +16 -0
  87. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  88. data/spec/dummy/config/initializers/session_store.rb +3 -0
  89. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  90. data/spec/dummy/config/locales/en.yml +23 -0
  91. data/spec/dummy/config/routes.rb +3 -0
  92. data/spec/dummy/config/secrets.yml +22 -0
  93. data/spec/dummy/db/migrate/20150226030314_enable_uuid_extension.rb +5 -0
  94. data/spec/dummy/db/migrate/20150226030315_create_users.rb +15 -0
  95. data/spec/dummy/db/migrate/20150226030316_create_pollett_contexts.pollett.rb +22 -0
  96. data/spec/dummy/db/schema.rb +49 -0
  97. data/spec/dummy/log/development.log +1315 -0
  98. data/spec/dummy/log/test.log +181283 -0
  99. data/spec/dummy/public/404.html +67 -0
  100. data/spec/dummy/public/422.html +67 -0
  101. data/spec/dummy/public/500.html +66 -0
  102. data/spec/dummy/public/favicon.ico +0 -0
  103. data/spec/factories/pollett_context.rb +5 -0
  104. data/spec/factories/pollett_key.rb +5 -0
  105. data/spec/factories/pollett_session.rb +5 -0
  106. data/spec/factories/user.rb +7 -0
  107. data/spec/mailers/pollett/mailer_spec.rb +73 -0
  108. data/spec/rails_helper.rb +19 -0
  109. data/spec/requests/keys_spec.rb +67 -0
  110. data/spec/requests/sessions_spec.rb +176 -0
  111. data/spec/requests/user_spec.rb +41 -0
  112. data/spec/spec_helper.rb +9 -0
  113. data/spec/support/email_helper.rb +9 -0
  114. metadata +323 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0b840bb4badc9859eb7fda1c68585d2deb6b3de4
4
+ data.tar.gz: ca1179661479d54174900d8af6181f78d3a31b2a
5
+ SHA512:
6
+ metadata.gz: 0680f8f973d73b0b09ab6e3d463a2e3b5f975dd44f69a23eaf114ad1d65c30f3d818d1da46d17040d34d631fcc9eb3b303d63a40d598efb92385937db2299a3d
7
+ data.tar.gz: 4e7e6e4c1e72cdb5e0d9c903554831a1e52b7a05a31cecfa9cd2b33e27c2b521fc9f412be3bc59794e8f290ac4790e938c488d49302b08eba218ed9fa3cb5c7f
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Jason Kriss
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # Pollett
2
+
3
+ Token-based authentication for your Rails API.
4
+
5
+ Pollett is a simple authentication library for your API-only Rails app. It treats sessions as a [first class domain concern]
6
+ (https://github.com/blog/1661-modeling-your-app-s-user-session) and takes its inspiration from [Clearance](https://github.com/thoughtbot/clearance).
7
+
8
+ Pollett currently requires Postgres and the use of UUID primary keys. This means you will need to have the `uuid-ossp` extension enabled before using Pollett.
9
+
10
+ ## Install
11
+
12
+ To get started, add Pollett to your `Gemfile`, `bundle install`, and run the
13
+ `install generator`:
14
+
15
+ ```sh
16
+ $ rails g pollett:install
17
+ ```
18
+
19
+ The generator:
20
+
21
+ * Inserts `Pollett::User` into your `User` model
22
+ * Inserts `Pollett::Controller` into your `ApplicationController`
23
+ * Creates an initializer to allow further configuration
24
+ * Creates a migration that either creates a users table or adds the necessary columns to the existing table
25
+ * Copies over the Session model migration
26
+ * Mounts the engine
27
+
28
+ Then, just migrate the database:
29
+
30
+ ```sh
31
+ $ rake db:migrate
32
+ ```
33
+
34
+ Finally, you must implement the `render_list` method in your `ApplicationController`. Use this method to paginate a list of records any way you want.
35
+
36
+ ## Configure
37
+
38
+ Override any of these defaults in `config/initializers/pollett.rb`:
39
+
40
+ ```ruby
41
+ Pollett.configure do |config|
42
+ config.user_model = ::User
43
+ config.minimum_password_length = 8
44
+ config.send_welcome_email = true
45
+ config.parent_mailer = ::ApplicationMailer
46
+ config.from_email = "noreply@example.com"
47
+ config.reset_url = ->(token) { "https://example.com/#{token}/reset" }
48
+ config.whitelist = []
49
+ end
50
+ ```
51
+
52
+ At minimum, you will need to configure `reset_url` so that the link will be correct in password reset emails. Also, if a default "from" email is not set in your `parent_mailer`, you will need to configure `from_email` as well.
53
+
54
+ ## Use
55
+
56
+ ### Access Control
57
+
58
+ Pollett authentication is opt-out rather than opt-in. This means that if there is an action that does not require authentication, you will need to use `skip_authentication`:
59
+
60
+ ```ruby
61
+ class ArticlesController < ApplicationController
62
+ skip_authentication only: :safe_action
63
+
64
+ def safe_action
65
+ # something that does not require authentication
66
+ end
67
+ end
68
+ ```
69
+
70
+ As you'd expect, `current_user` can be used from within controllers to access the authenticated user.
71
+
72
+ When authentication fails, Pollett raises a `Pollett::Unauthorized` error. You should [rescue_from](http://guides.rubyonrails.org/action_controller_overview.html#rescue-from) this to customize what is rendered.
73
+
74
+ ### Email
75
+ Pollett is capable of sending two types of emails. In addition to the standard password reset email, it will send a welcome email upon registration unless `config.send_welcome_email` is set to `false`.
76
+
77
+ These emails will make use of whatever `layout` you have specified in your `ApplicationMailer`. If you need to customize the subject of these emails or make minor tweaks to the messages, you can simply override them via [i18n translations]
78
+ (http://guides.rubyonrails.org/i18n.html). See [config/locales/en.yml](/config/locales/en.yml) for the
79
+ default behavior.
80
+
81
+ If you need to make more elaborate changes, you'll want to override the actual views. See [app/views/pollett/mailer](/app/views/pollett/mailer) for the default behavior.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Pollett'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('lib/**/*.rb')
14
+ end
15
+
16
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
17
+ load 'rails/tasks/engine.rake'
18
+
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+
23
+
24
+ Bundler::GemHelper.install_tasks
25
+
@@ -0,0 +1,37 @@
1
+ module Pollett
2
+ module Controller
3
+ extend ActiveSupport::Concern
4
+
5
+ include ActionController::HttpAuthentication::Token::ControllerMethods
6
+
7
+ included do
8
+ attr_accessor :current_context
9
+
10
+ before_action :authenticate!
11
+ end
12
+
13
+ module ClassMethods
14
+ def skip_authentication(options = {})
15
+ skip_before_action(:authenticate!, options)
16
+ end
17
+ end
18
+
19
+ private
20
+ def activate_context(context)
21
+ context.access(request)
22
+ self.current_context = context
23
+ end
24
+
25
+ def current_user
26
+ current_context.try(:user)
27
+ end
28
+
29
+ def authenticate!
30
+ if context = authenticate_with_http_token { |id, _| Context.authenticate(id) }
31
+ activate_context(context)
32
+ else
33
+ raise Unauthorized
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,4 @@
1
+ module Pollett
2
+ class ApplicationController < ::ApplicationController
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class KeysController < ApplicationController
3
+ include Concerns::Controllers::KeysController
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class SessionsController < ApplicationController
3
+ include Concerns::Controllers::SessionsController
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class UsersController < ApplicationController
3
+ include Concerns::Controllers::UsersController
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class Mailer < Pollett.config.parent_mailer
3
+ include Concerns::Mailers::Mailer
4
+ end
5
+ end
@@ -0,0 +1,40 @@
1
+ require "email_validator"
2
+
3
+ module Pollett
4
+ module User
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_secure_password
9
+
10
+ has_many :contexts, class_name: "Pollett::Context"
11
+ has_many :sessions, class_name: "Pollett::Session"
12
+ has_many :keys, class_name: "Pollett::Key"
13
+
14
+ before_validation :normalize_email
15
+
16
+ validates :name, presence: true
17
+
18
+ validates :email, presence: true,
19
+ uniqueness: true,
20
+ email: { strict_mode: true }
21
+
22
+ validates :password, length: { minimum: Pollett.config.minimum_password_length }, if: :password
23
+ end
24
+
25
+ module ClassMethods
26
+ def find_by_normalized_email(email)
27
+ find_by(email: normalize_email(email))
28
+ end
29
+
30
+ def normalize_email(email)
31
+ email.to_s.downcase.gsub(/\s+/, "")
32
+ end
33
+ end
34
+
35
+ private
36
+ def normalize_email
37
+ self.email = self.class.normalize_email(email)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class Context < ActiveRecord::Base
3
+ include Concerns::Models::Context
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class Key < Context
3
+ include Concerns::Models::Key
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class Session < Context
3
+ include Concerns::Models::Session
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class KeySerializer < ActiveModel::Serializer
3
+ include Concerns::Serializers::ContextSerializer
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class SessionSerializer < ActiveModel::Serializer
3
+ include Concerns::Serializers::ContextSerializer
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ class UserSerializer < ActiveModel::Serializer
2
+ include Pollett::Concerns::Serializers::UserSerializer
3
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class AuthenticateUser
3
+ include Concerns::Services::AuthenticateUser
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class ChangePassword
3
+ include Concerns::Services::ChangePassword
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class CreateSession
3
+ include Concerns::Services::CreateSession
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class RegisterUser
3
+ include Concerns::Services::RegisterUser
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Pollett
2
+ class ResetPassword
3
+ include Concerns::Services::ResetPassword
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ <%= t(".opening") %>
2
+
3
+ <%= @url %>
4
+
5
+ <%= t(".closing") %>
@@ -0,0 +1 @@
1
+ <%= t(".body") %>
@@ -0,0 +1,14 @@
1
+ en:
2
+ pollett:
3
+ mailer:
4
+ welcome:
5
+ subject: "Welcome!"
6
+ body: "Welcome aboard!"
7
+ reset:
8
+ subject: "Password Reset"
9
+ opening: "Someone recently requested a password change for your account. To reset your password, just follow this link:"
10
+ closing: "If you did not make this request, just ignore this email. Your password will remain unchanged."
11
+ activerecord:
12
+ attributes:
13
+ user:
14
+ password_digest: "Password"
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ Pollett::Engine.routes.draw do
2
+ scope shallow: true, format: false do
3
+ resource :user, only: [:show, :update, :destroy]
4
+
5
+ resources :sessions, except: [:new, :edit, :update] do
6
+ post :forgot, on: :collection
7
+ end
8
+
9
+ resources :keys, except: [:new, :edit, :update]
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ class CreatePollettContexts < ActiveRecord::Migration
2
+ def change
3
+ create_table :pollett_contexts, id: :uuid do |t|
4
+ t.string :type, null: false
5
+ t.uuid :user_id, null: false
6
+ t.string :client, null: false
7
+ t.datetime :revoked_at
8
+ t.datetime :accessed_at
9
+ t.string :ip
10
+ t.string :user_agent
11
+
12
+ t.timestamps null: false
13
+ end
14
+
15
+ add_foreign_key :pollett_contexts, :users, on_delete: :cascade
16
+
17
+ add_index :pollett_contexts, :user_id
18
+ add_index :pollett_contexts, :accessed_at
19
+ add_index :pollett_contexts, :revoked_at
20
+ end
21
+ end
@@ -0,0 +1,123 @@
1
+ require "rails/generators/base"
2
+ require "rails/generators/active_record"
3
+
4
+ module Pollett
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ source_root File.expand_path("../templates", __FILE__)
10
+
11
+ def mount
12
+ inject_into_file "config/routes.rb", after: "Rails.application.routes.draw do\n" do
13
+ " mount Pollett::Engine => \"/\"\n\n"
14
+ end
15
+ end
16
+
17
+ def create_initializer
18
+ copy_file "initializer.rb", "config/initializers/pollett.rb"
19
+ end
20
+
21
+ def inject_into_application_controller
22
+ inject_into_class "app/controllers/application_controller.rb", ApplicationController do
23
+ " include Pollett::Controller\n"
24
+ end
25
+ end
26
+
27
+ def create_or_inject_into_user_model
28
+ if File.exist? "app/models/user.rb"
29
+ inject_into_file "app/models/user.rb", after: "class User < ActiveRecord::Base\n" do
30
+ " include Pollett::User\n\n"
31
+ end
32
+ else
33
+ copy_file "user.rb", "app/models/user.rb"
34
+ end
35
+ end
36
+
37
+ def create_users_migration
38
+ if users_table_exists?
39
+ create_add_columns_migration
40
+ else
41
+ copy_migration "create_users.rb"
42
+ end
43
+ end
44
+
45
+ def install_migrations
46
+ Dir.chdir(Rails.root) { `rake pollett:install:migrations` }
47
+ end
48
+
49
+ private
50
+ def users_table_exists?
51
+ ActiveRecord::Base.connection.table_exists?(:users)
52
+ end
53
+
54
+ def create_add_columns_migration
55
+ if migration_needed?
56
+ config = {
57
+ new_columns: new_columns,
58
+ new_indexes: new_indexes
59
+ }
60
+
61
+ copy_migration("add_pollett_to_users.rb", config)
62
+ end
63
+ end
64
+
65
+ def migration_needed?
66
+ new_columns.any? || new_indexes.any?
67
+ end
68
+
69
+ def new_columns
70
+ @new_columns ||= {
71
+ email: "t.string :name, null: false",
72
+ email: "t.string :email, null: false",
73
+ password_digest: "t.string :password_digest, null: false",
74
+ reset_token: "t.string :reset_token"
75
+ }.reject { |column| existing_users_columns.include?(column.to_s) }
76
+ end
77
+
78
+ def new_indexes
79
+ @new_indexes ||= {
80
+ index_users_on_email: "add_index :users, :email, unique: true",
81
+ index_users_on_reset_token: "add_index :users, :reset_token, unique: true"
82
+ }.reject { |index| existing_users_indexes.include?(index.to_s) }
83
+ end
84
+
85
+ def copy_migration(migration_name, config = {})
86
+ unless migration_exists?(migration_name)
87
+ migration_template(
88
+ "db/migrate/#{migration_name}",
89
+ "db/migrate/#{migration_name}",
90
+ config
91
+ )
92
+ end
93
+ end
94
+
95
+ def migration_exists?(name)
96
+ existing_migrations.include?(name)
97
+ end
98
+
99
+ def existing_migrations
100
+ @existing_migrations ||= Dir.glob("db/migrate/*.rb").map do |file|
101
+ migration_name_without_timestamp(file)
102
+ end
103
+ end
104
+
105
+ def migration_name_without_timestamp(name)
106
+ name.sub(%r{^.*(db/migrate/)(?:\d+_)?}, "")
107
+ end
108
+
109
+ def existing_users_columns
110
+ ActiveRecord::Base.connection.columns(:users).map(&:name)
111
+ end
112
+
113
+ def existing_users_indexes
114
+ ActiveRecord::Base.connection.indexes(:users).map(&:name)
115
+ end
116
+
117
+ # for generating a timestamp when using `create_migration`
118
+ def self.next_migration_number(dir)
119
+ ActiveRecord::Generators::Base.next_migration_number(dir)
120
+ end
121
+ end
122
+ end
123
+ end