Skip to content

Commit 1c84b96

Browse files
authored
Merge pull request #22 from graphql-devise/add-generators
Add routes generator
2 parents db25112 + 042fc24 commit 1c84b96

8 files changed

Lines changed: 235 additions & 14 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@
2323
.ruby-gemset
2424

2525
.env
26+
/spec/tmp/config/routes.rb

README.md

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,40 @@ Or install it yourself as:
2020

2121
$ gem install graphql_devise
2222

23-
## Usage
24-
All configurations for [Devise](https://github.com/plataformatec/devise) and
25-
[Devise Token Auth](https://github.com/lynndylanhurley/devise_token_auth) are
26-
available, so you can read the docs there to customize your options.
23+
Next, you need to run the generator:
24+
25+
$ rails generate graphql_devise:install
26+
27+
Graphql Devise generator will execute `Devise` and `Devise Token Auth`
28+
generators for you. These will make the required changes for the gems to
29+
work correctly. All configurations for [Devise](https://github.com/plataformatec/devise) and
30+
[Devise Token Auth](https://github.com/lynndylanhurley/devise_token_auth) are available,
31+
so you can read the docs there to customize your options.
2732
Configurations are done via initializer files as usual, one per gem.
28-
You can generate most of the configuration by using DTA's installer while we work
29-
on our own generators like this
33+
34+
The generator accepts 2 params: `user_class` and `mount_path`. The params
35+
will be used to mount the route in `config/routes.rb`. For instance the executing:
36+
3037
```bash
31-
$ rails g devise_token_auth:install User auth
38+
$ rails g graphql_devise:install Admin api/auth
3239
```
33-
`User` could be any model name you are going to be using for authentication,
34-
and `auth` could be anything as we will be removing that from the routes file.
3540

36-
### Mounting Routes
37-
First, you need to mount the gem in the routes file like this
41+
Will do the following:
42+
- Execute `Devise` install generator
43+
- Execute `Devise Token Auth` install generator with `Admin` and `api/auth` as params
44+
- Find or create `Admin` model
45+
- Add `devise` modules to `Admin` model
46+
- Other changes that you can find [here](https://devise-token-auth.gitbook.io/devise-token-auth/config)
47+
- Add the route to `config/routes.rb`
48+
- `mount_graphql_devise_for 'Admin', at: 'api/auth'
49+
50+
`Admin` could be any model name you are going to be using for authentication,
51+
and `api/auth` could be any mount path you would like to use for auth.
52+
53+
### Mounting Routes manually
54+
Routes can be added using the initializer or manually.
55+
You can add a route like this:
56+
3857
```ruby
3958
# config/routes.rb
4059

@@ -50,8 +69,6 @@ Rails.application.routes.draw do
5069
)
5170
end
5271
```
53-
If you used DTA's installer you will have to remove the `mount_devise_token_auth_for`
54-
line.
5572

5673
Here are the options for the mount method:
5774

@@ -107,6 +124,9 @@ class User < ApplicationRecord
107124
end
108125
```
109126

127+
The install generator can do this for you if you specify the `user_class` option.
128+
See [Installation](#Installation) for details.
129+
110130
### Customizing Email Templates
111131
The approach of this gem is a bit different from DeviseTokenAuth. We have placed our templates in `app/views/graphql_devise/mailer`,
112132
so if you want to change them, place yours on the same dir structure on your Rails project. You can customize these two templates:
@@ -132,6 +152,9 @@ class MyController < ApplicationController
132152
end
133153
```
134154

155+
The install generator can do this for you because it executes DTA installer.
156+
See [Installation](#Installation) for details.
157+
135158
### Making Requests
136159
Here is a list of the available mutations and queries assuming your mounted model
137160
is `User`.
@@ -167,7 +190,6 @@ templates.
167190
## Future Work
168191
We will continue to improve the gem and add better docs.
169192

170-
1. Add install generator.
171193
1. Add mount option that will create a separate schema for the mounted resource.
172194
1. Make sure this gem can correctly work alongside DTA and the original Devise gem.
173195
1. Improve DOCS.

graphql_devise.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ Gem::Specification.new do |spec|
4141
spec.add_development_dependency 'rubocop-rspec'
4242
spec.add_development_dependency 'sqlite3', '~> 1.3'
4343
spec.add_development_dependency 'github_changelog_generator'
44+
spec.add_development_dependency 'generator_spec'
4445
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module GraphqlDevise
2+
class InstallGenerator < ::Rails::Generators::Base
3+
source_root File.expand_path('templates', __dir__)
4+
5+
argument :user_class, type: :string, default: 'User'
6+
argument :mount_path, type: :string, default: 'auth'
7+
8+
def execute_devise_installer
9+
generate 'devise:install'
10+
end
11+
12+
def execute_dta_installer
13+
generate 'devise_token_auth:install', "#{user_class} #{mount_path}"
14+
end
15+
16+
def mount_resource_route
17+
routes_file = 'config/routes.rb'
18+
routes_path = File.join(destination_root, routes_file)
19+
gem_helper = 'mount_graphql_devise_for'
20+
gem_route = "#{gem_helper} '#{user_class}', at: '#{mount_path}'"
21+
dta_route = "mount_devise_token_auth_for '#{user_class}', at: '#{mount_path}'"
22+
file_start = 'Rails.application.routes.draw do'
23+
24+
if File.exist?(routes_path)
25+
current_route = parse_file_for_line(routes_path, gem_route)
26+
27+
if current_route.present?
28+
say_status('skipped', "Routes already exist for #{user_class} at #{mount_path}")
29+
else
30+
current_dta_route = parse_file_for_line(routes_path, dta_route)
31+
32+
if current_dta_route.present?
33+
replace_line(routes_path, dta_route, gem_route)
34+
else
35+
insert_text_after_line(routes_path, file_start, gem_route)
36+
end
37+
end
38+
else
39+
say_status('skipped', "#{routes_file} not found. Add \"#{gem_route}\" to your routes file.")
40+
end
41+
end
42+
43+
private
44+
45+
def insert_text_after_line(filename, line, str)
46+
gsub_file(filename, /(#{Regexp.escape(line)})/mi) do |match|
47+
"#{match}\n #{str}"
48+
end
49+
end
50+
51+
def replace_line(filename, old_line, new_line)
52+
gsub_file(filename, /(#{Regexp.escape(old_line)})/mi, " #{new_line}")
53+
end
54+
55+
def parse_file_for_line(filename, str)
56+
match = false
57+
58+
File.open(filename) do |f|
59+
f.each_line do |line|
60+
match = line if line =~ /(#{Regexp.escape(str)})/mi
61+
end
62+
end
63+
match
64+
end
65+
end
66+
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2+
3+
en:
4+
devise:
5+
confirmations:
6+
confirmed: "Your email address has been successfully confirmed."
7+
send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
8+
send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
9+
failure:
10+
already_authenticated: "You are already signed in."
11+
inactive: "Your account is not activated yet."
12+
invalid: "Invalid %{authentication_keys} or password."
13+
locked: "Your account is locked."
14+
last_attempt: "You have one more attempt before your account is locked."
15+
not_found_in_database: "Invalid %{authentication_keys} or password."
16+
timeout: "Your session expired. Please sign in again to continue."
17+
unauthenticated: "You need to sign in or sign up before continuing."
18+
unconfirmed: "You have to confirm your email address before continuing."
19+
mailer:
20+
confirmation_instructions:
21+
subject: "Confirmation instructions"
22+
reset_password_instructions:
23+
subject: "Reset password instructions"
24+
unlock_instructions:
25+
subject: "Unlock instructions"
26+
email_changed:
27+
subject: "Email Changed"
28+
password_change:
29+
subject: "Password Changed"
30+
omniauth_callbacks:
31+
failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
32+
success: "Successfully authenticated from %{kind} account."
33+
passwords:
34+
no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
35+
send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
36+
send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
37+
updated: "Your password has been changed successfully. You are now signed in."
38+
updated_not_active: "Your password has been changed successfully."
39+
registrations:
40+
destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
41+
signed_up: "Welcome! You have signed up successfully."
42+
signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
43+
signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
44+
signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
45+
update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address."
46+
updated: "Your account has been updated successfully."
47+
updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again"
48+
sessions:
49+
signed_in: "Signed in successfully."
50+
signed_out: "Signed out successfully."
51+
already_signed_out: "Signed out successfully."
52+
unlocks:
53+
send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
54+
send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
55+
unlocked: "Your account has been unlocked successfully. Please sign in to continue."
56+
errors:
57+
messages:
58+
already_confirmed: "was already confirmed, please try signing in"
59+
confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
60+
expired: "has expired, please request a new one"
61+
not_found: "not found"
62+
not_locked: "was not locked"
63+
not_saved:
64+
one: "1 error prohibited this %{resource} from being saved:"
65+
other: "%{count} errors prohibited this %{resource} from being saved:"
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Generators are not automatically loaded by Rails
2+
require 'rails_helper'
3+
require 'generators/graphql_devise/install_generator'
4+
5+
RSpec.describe GraphqlDevise::InstallGenerator, type: :generator do
6+
destination File.expand_path('../../../tmp', __FILE__)
7+
8+
before do
9+
prepare_destination
10+
end
11+
12+
let(:routes_path) { "#{destination_root}/config/routes.rb" }
13+
let(:routes_content) { File.read(routes_path) }
14+
let(:dta_route) { "mount_devise_token_auth_for 'User', at: 'auth'" }
15+
16+
xcontext 'when the file exists' do
17+
before do
18+
create_file_with_content(
19+
routes_path,
20+
"Rails.application.routes.draw do\n#{dta_route}\nend"
21+
)
22+
end
23+
24+
context 'when passing no params to the generator' do
25+
before { run_generator }
26+
27+
it 'replaces dta route using the default values for class and path' do
28+
generator_added_route = / mount_graphql_devise_for 'User', at: 'auth'/
29+
expect(routes_content).to match(generator_added_route)
30+
expect(routes_content).not_to match(dta_route)
31+
end
32+
end
33+
34+
context 'when passing custom params to the generator' do
35+
before { run_generator %w[Admin api] }
36+
37+
it 'add the routes using the provided values for class and path and keeps dta route' do
38+
generator_added_route = / mount_graphql_devise_for 'Admin', at: 'api'/
39+
expect(routes_content).to match(generator_added_route)
40+
expect(routes_content).to match(dta_route)
41+
end
42+
end
43+
end
44+
45+
xcontext 'when file does *NOT* exist' do
46+
before { run_generator }
47+
48+
it 'does *NOT* create the file and throw no exception' do
49+
expect(File.exist?(routes_path)).to be_falsey
50+
end
51+
end
52+
end

spec/rails_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# Add additional requires below this line. Rails is not loaded until this point!
1111
require 'factory_bot'
1212
require 'faker'
13+
require 'generator_spec'
1314

1415
# Load RSpec helpers.
1516
Dir[File.join(ENGINE_ROOT, 'spec/support/**/*.rb')].each { |f| require f }
@@ -36,4 +37,5 @@
3637
config.include(Requests::JsonHelpers, type: :request)
3738
config.include(Requests::AuthHelpers, type: :request)
3839
config.include(ActiveSupport::Testing::TimeHelpers)
40+
config.include(Generators::FileHelpers, type: :generator)
3941
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
require 'fileutils'
2+
3+
module Generators
4+
module FileHelpers
5+
def create_file_with_content(path, content)
6+
FileUtils.mkdir(File.dirname(path))
7+
File.open(path, 'w') do |f|
8+
f.write(content)
9+
end
10+
end
11+
end
12+
end

0 commit comments

Comments
 (0)