Stemplin is a time tracking application written in Ruby on Rails.
See the contribution guidelines in:
https://github.com/rubynor/stemplin/blob/main/CONTRIBUTING.md
Install the project's dependencies by running:
bundle install
rails assets:precompile
rails db:create
rails db:migrate
yarn install
Populate the database from fixtures:
rails db:fixtures:load
Hotwire will not work without Redis. If it is not running, start it with:
redis-server --daemonize yes
Finally, you can run your project locally with:
bin/dev
Open your browser and visit http://localhost:3000, your project should be running!
Run linter with:
bin/rubocop
Or run autocorrection with:
bin/rubocop -a
This project uses ActionPolicy for authorization.
-
ALWAYS use
authorized_scope
when querying the database, to prevent data leakage. -
Use
authorize!
in EVERY SINGLE controller action, and create a policy for EVERY SINGLE controller action.
Policies are used to limit a current_user
's access to controller methods.
Policies are defined like so:
-
user
holds the value ofcurrent_user
-
record
holds the value of whatever is passed in to theauthorize!
method. If nothing is passed,record
will hold the model class, that is based on the controller name. In this case that class isClient
# app/policies/time_reg_policy.rb
class TimeRegPolicy < ApplicationPolicy
def index?
# Allows all users to access the index action
true
end
end
# app/policies/client_policy.rb
class ClientPolicy < ApplicationPolicy
def index?
# As the index action fetches an entire collection, `record` is not relevant
# This allows organization_admins to access the action
user.organization_admin?
end
def create?
# Allows organization_admins in the Client's organization access to the action
user.organization_admin? && user.current_organization == record.oragnization
end
end
Scopes are used to scope out records that the current_user
can access in a collection.
Define a scope like so:
# app/policies/time_reg_policy.rb
class TimeRegPolicy < ApplicationPolicy
scope_for :relation, :own do |relation|
# Scopes out the user's own TimeRegs
relation.joins(:organization, :user).where(organizations: user.current_organization, user: user).distinct
end
end
# app/policies/client_policy.rb
class ClientPolicy < ApplicationPolicy
scope_for :relation do |relation|
# Scopes out Clients accessible for organization_admin
if user.organization_admin?
relation.joins(:organization).where(organizations: user.current_organization).distinct
else
relation.none
end
end
end
# app/controllers/clients_controller.rb
class ClientsController < ApplicationController
def index
# Where the controller fetches an entire collection,
# use the `authorize!` method without passing in a record (implicitly)
authorize!
@clients = authorized_scope(Client, type: :relation).all
end
def show
@client = authorized_scope(Client, type: :relation).find(params[:id])
# Where the controller fetches a single record,
# use the `authorize!` method passing in a record (explicitly)
authorize! @client
end
def create
# Use `authorized_scope` when initializing a record
@client = authorized_scope(Client, type: :relation).new(client_params)
# Use `authorize!` before saving a record
authorize! @clinet
@client.save!
end
end