Skip to content

Commit

Permalink
add friendly id to accounts
Browse files Browse the repository at this point in the history
  • Loading branch information
simonfranzen committed Sep 12, 2020
1 parent 760de84 commit e0996bb
Show file tree
Hide file tree
Showing 16 changed files with 209 additions and 14 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ gem 'rails_admin-i18n' # Use default rails_admin translatio
gem 'cancancan' # Defining abilities
gem 'image_processing', '~> 1.2' # Image processing
gem 'mini_magick' # Image manipulation with rmagick
gem 'friendly_id', '5.3.0' # Auto generate slugs for resources

# gem 'graphiql-rails', group: :development

Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ GEM
tty-screen (~> 0.6.5)
tty-tree (~> 0.3.0)
ffi (1.11.1)
friendly_id (5.3.0)
activerecord (>= 4.0.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
graphql (1.11.4)
Expand Down Expand Up @@ -357,6 +359,7 @@ DEPENDENCIES
dotenv-rails
factory_bot_rails
faker (~> 1.8)
friendly_id (= 5.3.0)
graphql (~> 1.11.4)
graphql-auth!
graphql-errors
Expand Down
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ This boilerplate works like a charm with the following gemset:
- mini_magick
- puma
- bootsnap
- friendly_id


## 🚀 Quick start
Expand Down Expand Up @@ -141,7 +142,32 @@ Set user with `HTTP_AUTH_USER` and password with `HTTP_AUTH_PASSWORD`.

We enable HTTP auth currently for all controllers. The `ApplicationController` class includes the concern `HttpAuth`. Feel free to change it.

### 13. Testing
### 13. Auto generated slugs
To provider more user friendly urls for your frontend we are using [friendly_id](https://github.com/norman/friendly_id) to auto generate slugs for models. We have already implemented it for the `Account` model. For more configuration see `config/initializers/friendly_id.rb`.

To create a new slug field for a model add a field `slug`:

```sh
$ rails g migration add_slug_to_resource slug:uniq
$ bundle exec rake db:migrate
```

Edit your model file as the following:

```ruby
class Account < ApplicationRecord
extend FriendlyId
friendly_id :name, use: :slugged
end
```

Replace tradinional `Account.find(params[:id])` with `Account.friendly.find(params[:id])`
```ruby
account = Account.friendly.find(params[:id])
```


### 14. Testing

We are using the wonderful framework [rspec](https://github.com/rspec/rspec). The testsuit also uses [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails) for fixtures.

Expand All @@ -165,11 +191,11 @@ Open test coverage results with
$ open /coverage/index.html
```

### 14. Linter with Rubocop
### 15. Linter with Rubocop

We are using the wonderful [rubocop](https://github.com/rubocop-hq/rubocop-rails) to lint and autofix the code. Install the rubocop VSCode extension to get best experience during development.

### 15. Sending emails
### 16. Sending emails
Set your SMTP settings with these environment variables:
- `SMTP_ADDRESS`
- `SMTP_PORT`
Expand All @@ -185,7 +211,7 @@ Have a look at `config/environments/production.rb` where we set the `config.acti
Set the email address for your `ApplicationMailer` and devise emails with env var `DEVISE_MAILER_FROM`.


### 16. Deployment
### 17. Deployment
The project runs on every webhoster with ruby installed. The only dependency is a PostgreSQL database. Create a block `production:` in the`config/database.yml` for your connection.

#### Heroku
Expand All @@ -206,7 +232,7 @@ The pipeline has 2 environments: staging and production. Staging pipline is gett
It also triggers pipeline while opening a PR.

## What's missing?
- Update and Delete mutation for admin of an account
- Update and Delete mutations for admin of an account

Feel free to make feature requrest or join development!

Expand Down
4 changes: 3 additions & 1 deletion app/graphql/types/account_type.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# frozen_string_literal: true

module Types
# GraphQL type for a user
# GraphQL type for an account
class AccountType < BaseModel
field :name, String, null: false
field :slug, String, null: true
# field :users, [::Types::UserType], null: true
end
end
7 changes: 4 additions & 3 deletions app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ def initialize(user)
return if user.new_record?

if user.superadmin?
can :access, :rails_admin # grant access to rails_admin
can :manage, :all # admins can manage all objects
elsif user.admin?
can :access, :rails_admin # grant access to rails_admin
end

if user.admin? || user.superadmin?
can :crud, User, account_id: user.account_id
else
can :read, User, account_id: user.account_id
Expand Down
24 changes: 24 additions & 0 deletions app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,36 @@
#
# id :uuid not null, primary key
# name :string
# slug :string
# users_count :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_accounts_on_slug (slug) UNIQUE
#
class Account < ApplicationRecord
extend FriendlyId

# - EXTENSIONS
friendly_id :name, use: :slugged

# - VALIDATIONS
validates :name, presence: true
validates :name, length: { maximum: 255 }
validates :slug, length: { maximum: 255 }

# - RELATIONS
has_many :users, dependent: :destroy


# override friendly id checker for categories
def should_generate_new_friendly_id?
(slug.nil? || slug.blank?) || (name_changed? && !slug_changed?)
end


# :nocov:
rails_admin do
weight 10
Expand All @@ -23,16 +44,19 @@ class Account < ApplicationRecord
list do
field :id
field :name
field :slug
field :users_count
end

edit do
field :name
field :slug
end

show do
field :id
field :name
field :slug
field :users_count
end
end
Expand Down
2 changes: 0 additions & 2 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ class Application < Rails::Application
config.autoload_paths << Rails.root.join('lib')
config.eager_load_paths << Rails.root.join('lib')

# config.autoload_paths += Dir[Rails.root.join('app', 'graphql')]

# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
Expand Down
89 changes: 89 additions & 0 deletions config/initializers/friendly_id.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# frozen_string_literal: true

# FriendlyId Global Configuration
#
# Use this to set up shared configuration options for your entire application.
# Any of the configuration options shown here can also be applied to single
# models by passing arguments to the `friendly_id` class method or defining
# methods in your model.
#
# To learn more, check out the guide:
#
# http://norman.github.io/friendly_id/file.Guide.html
FriendlyId.defaults do |config|
# ## Reserved Words
#
# Some words could conflict with Rails's routes when used as slugs, or are
# undesirable to allow as slugs. Edit this list as needed for your app.
config.use :reserved

config.reserved_words = %w[new edit index session login logout users admin
stylesheets assets javascripts images]

# ## Friendly Finders
#
# Uncomment this to use friendly finders in all models. By default, if
# you wish to find a record by its friendly id, you must do:
#
# MyModel.friendly.find('foo')
#
# If you uncomment this, you can do:
#
# MyModel.find('foo')
#
# This is significantly more convenient but may not be appropriate for
# all applications, so you must explicity opt-in to this behavior. You can
# always also configure it on a per-model basis if you prefer.
#
# Something else to consider is that using the :finders addon boosts
# performance because it will avoid Rails-internal code that makes runtime
# calls to `Module.extend`.
#
# config.use :finders
#
# ## Slugs
#
# Most applications will use the :slugged module everywhere. If you wish
# to do so, uncomment the following line.
#
# config.use :slugged
#
# By default, FriendlyId's :slugged addon expects the slug column to be named
# 'slug', but you can change it if you wish.
#
# config.slug_column = 'slug'
#
# When FriendlyId can not generate a unique ID from your base method, it appends
# a UUID, separated by a single dash. You can configure the character used as the
# separator. If you're upgrading from FriendlyId 4, you may wish to replace this
# with two dashes.
#
# config.sequence_separator = '-'
#
# ## Tips and Tricks
#
# ### Controlling when slugs are generated
#
# As of FriendlyId 5.0, new slugs are generated only when the slug field is
# nil, but if you're using a column as your base method can change this
# behavior by overriding the `should_generate_new_friendly_id` method that
# FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave
# more like 4.0.
#
# config.use Module.new {
# def should_generate_new_friendly_id?
# slug.blank? || <your_column_name_here>_changed?
# end
# }
#
# FriendlyId uses Rails's `parameterize` method to generate slugs, but for
# languages that don't use the Roman alphabet, that's not usually sufficient.
# Here we use the Babosa library to transliterate Russian Cyrillic slugs to
# ASCII. If you use this, don't forget to add "babosa" to your Gemfile.
#
# config.use Module.new {
# def normalize_friendly_id(text)
# text.to_slug.normalize! :transliterations => [:russian, :latin]
# end
# }
end
2 changes: 2 additions & 0 deletions config/locales/activerecord/de.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
de:
attributes:
slug: Pfad
activerecord:
attributes:
account:
Expand Down
4 changes: 3 additions & 1 deletion config/locales/activerecord/en.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
en:
attributes:
slug: Slug
activerecord:
attributes:
account:
Expand Down Expand Up @@ -28,7 +30,7 @@ en:
unconfirmed_email: Unconfirmed email
unlock_token: Unlock token
updated_at: Updated at
first_name: sFirstname
first_name: Firstname
last_name: Lastname
role: Role
models:
Expand Down
6 changes: 6 additions & 0 deletions db/migrate/20200912153138_add_slug_to_accounts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddSlugToAccounts < ActiveRecord::Migration[6.0]
def change
add_column :accounts, :slug, :string
add_index :accounts, :slug, unique: true
end
end
17 changes: 17 additions & 0 deletions db/migrate/20200912153858_create_friendly_id_slugs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class CreateFriendlyIdSlugs < ActiveRecord::Migration[6.0]
def change
create_table :friendly_id_slugs do |t|
t.string :slug, :null => false
t.integer :sluggable_id, :null => false
t.string :sluggable_type, :limit => 50
t.string :scope
t.datetime :created_at
end
add_index :friendly_id_slugs, :sluggable_id
add_index :friendly_id_slugs, [:slug, :sluggable_type]
add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true
add_index :friendly_id_slugs, :sluggable_type

Account.all.each(&:save)
end
end
16 changes: 15 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2020_09_12_120337) do
ActiveRecord::Schema.define(version: 2020_09_12_153858) do

# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
Expand All @@ -21,6 +21,8 @@
t.integer "users_count"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "slug"
t.index ["slug"], name: "index_accounts_on_slug", unique: true
end

create_table "active_storage_attachments", force: :cascade do |t|
Expand All @@ -44,6 +46,18 @@
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end

create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope"
t.datetime "created_at"
t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id"
t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type"
end

create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "first_name", default: "", null: false
t.string "last_name", default: "", null: false
Expand Down
2 changes: 1 addition & 1 deletion db/seeds.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
user = User.create!(
user = User.create(
email: ENV['ADMIN_EMAIL'],
password: ENV['ADMIN_PASSWORD'],
password_confirmation: ENV['ADMIN_PASSWORD'],
Expand Down
5 changes: 5 additions & 0 deletions spec/factories/accounts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
#
# id :uuid not null, primary key
# name :string
# slug :string
# users_count :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_accounts_on_slug (slug) UNIQUE
#
FactoryBot.define do
factory :account do
name { 'My Company' }
Expand Down
Loading

0 comments on commit e0996bb

Please sign in to comment.