# Ruby & Rails guidelines

We follow the `Rubocop` official [Rubocop Ruby coding style guide](https://github.com/rubocop/ruby-style-guide) as the primary source of best practices and conventions.

* 1. [Do's and Don'ts](#1-dos-and-donts)
  * 1.1. [Environment Variables](#11-use-dotenv-for-environment-variables)
  * 1.2. [Loading in batches](#12-loading-in-batches)
  * 1.3. [Avoid Active Record callbacks with side effects](#13-avoid-active-record-callbacks-with-side-effects)
  * 1.4. [Avoid raw SQL queries](#14-avoid-raw-sql-queries)
  * 1.5. [Size instead of count](#15-size-instead-of-count)
  * 1.6. [Avoid N+1 Queries with includes](#16-avoid-n1-queries-with-includes)
  * 1.7. [Avoid Default Scope](#17-avoid-default-scope)
  * 1.8. [Use find\_by for instead where().first](#18-use-findby-for-instead-wherefirst)
  * 1.9. [Check constraints](#19-check-database-constraints)
  * 1.10. [Filter sensitive parameters in logs](#110-filter-sensitive-parameters-in-logs)
* 2. [General project organization and architecture](#2-general-project-organization-and-architecture)
  * 2.1. [Project structure example](#21-project-structure-example)
* 3. [Common Patterns](#3-common-patterns)
  * 3.1. [Devise (Authentication)](#31-devise-authentication)
  * 3.2. [Testing](#32-testing)
    * 3.2.1 [Testing best practices](#321-testing-best-practices)
      * 3.2.1.1 [Use Let](#3211-use-let)
      * 3.2.1.2 [Use Factories](#3212-use-factories)
      * 3.2.1.3 [Describe Methods](#3213-describe-methods)
* 4. [Gems](#4-gems)

## 1. Do's and Don'ts

Add and follow the official [MarsBased Rubocop configuration](https://github.com/MarsBased/marstyle/blob/master/ruby/.rubocop.yml), where most of rules are already defined, highlighting these two:

* Use single quotes when possible.
* Max length of 90 characters

### 1.1. Use Dotenv for environment variables

We use the [Dotenv](https://github.com/bkeepers/dotenv) gem for managing the environment variables.

### 1.2. Loading in batches

Don't iterate unlimited / big queries directly. Use find in batches for loading big queries:

```ruby
# WRONG
Car.all.each do |car|
  car.start_engine!
end

# RIGHT
Car.find_each do |car|
  car.start_engine!
end
```

### 1.3. Avoid Active Record callbacks with side effects

Avoid using Active Record callbacks unless it's related to data persistence specially avoiding side effects like sending and e-email.

Consider using the [command pattern](https://github.com/MarsBased/handbook/blob/master/guides/patterns/rails/command.md) to send the e-mail from a controller action.

```ruby
# WRONG
after_save :notify_user

def notify_user
  UserMailer.notify(user).deliver
end
```

### 1.4. Avoid raw SQL queries

Avoid writing raw SQL queries unless strictly necessary.

When using Active Record we have the full power of it. For example if we have a custom serialization for a column, Active Record will automatically convert the value when writing queries.

```ruby
# WRONG
User.where('active = ?', params[:active])

# RIGHT
User.where(active: params[:active])
```

When writing more complex queries you may use Arel or write the where clause manually. However take into account that if you write it manually you won't have the full power of Active Record, like:

* You will not be able to use alias attributes.
* You will not be able to use custom types (serialization and deserialization).

```ruby
class User < ApplicationRecord
  alias_attribute :created_at, :dtCreationDate
end

# MANUAL
User.where('dtCreationDate < ?', DateTime.current) # Needs to use column name in the database

# AREL
User.where(User.arel_table[:created_at].lt(DateTime.current)) # Can use aliased name
```

### 1.5. Size instead of count

Use `size` instead of `count` unless you are doing a direct count on a table. Using `count` always triggers a query while using `size` is able to use the cached values of a previous query.

```ruby
# WRONG
Post.published.count

# RIGHT
Post.published.size

# RIGHT
Post.count # Counting directly on the model class
```

### 1.6. Avoid N+1 Queries with includes

When you have to access an association, avoid N+1 query problems, you can use `includes` to eager load the associated records:

```ruby
# WRONG
User.all.each do |user|
  user.posts.each do |post|
    p post.title
  end
end

# RIGHT
User.includes(:posts).each do |user|
  user.posts.each do |post|
    p post.title
  end
end
```

You can find some more examples in the [Active Record guide](/our-development-guides/activerecord-guide.md).

### 1.7. Avoid Default Scope

In order to avoid unexpected and hidden behaviour, avoid using default\_scope and use named scopes and explicit uses of those scopes:

```ruby
# WRONG
class User < ActiveRecord::Base
  default_scope { where(deleted: false) }
end

# RIGHT
class User < ActiveRecord::Base
  scope :active, -> { where(deleted: false) }
end
```

### 1.8. Use find\_by for instead where().first

When retrieving a single record from the database, don’t use `where(...).first`, use `find_by` instead. And similarly when selecting a single item from a collection use `find { ... }` instead of `select { ... }.first`.

```ruby
# WRONG
User.where(active: true).first

# RIGHT
User.find_by(active: true)
```

### 1.9. Check database constraints

Check that constraints are correct and that they match the validations. A typical example is adding a default without a not-null constraint.

### 1.10. Filter sensitive parameters in logs

When receiving parameters in a controller that contain sensitive information like a password or secret key, add the name of the parameter to the list of filtered parameters. Note that `:password` is already filtered by default.

```ruby
Rails.application.config.filter_parameters += [:api_key, :secret]
```

## 2. General project organization and architecture

Follow the standard generated directory structure at project initialization with `rails new project_name` as described in [Ruby On Rails Guide](https://guides.rubyonrails.org/getting_started.html).

Additionally:

* Services under the `/app/services` directory.
* Commands under the `/app/commands` directory.
* Presenters under the `/app/presenters` directory.
* Query objects under the `/app/queries` directory.
* Form objects under the `/app/form_objects` directory.

### 2.1. Project structure example

```
app/
 |- assets/
 |- channels/
 |- controllers/
 |- helpers/
 |- jobs/
 |- mailers/
 |- models/
 |- form_objects/
 |- queries/
 |- presenters/
 |- services/
 |- commands/
 |- views/
bin/
config/
 |-environments/
 |-initializers/
 |-locales/
db/
 |- migrate/
lib/
 |-assets/
 |-tasks/
log/
public/
spec/
 |- factories/
 |- helpers/
 |- mailers/
 |- models/
 |- requests/
 |- support/
 |- views/
tmp/
vendor/
```

## 3. Common Patterns

* [Presenter](https://github.com/MarsBased/handbook/guides/patterns/rails/presenter.md)
* [Command](https://github.com/MarsBased/handbook/guides/patterns/rails/command.md)
* [Form Composition](https://github.com/MarsBased/handbook/guides/patterns/rails/form-composition.md)
* [Query Object](https://github.com/MarsBased/handbook/guides/patterns/rails/query-object.md)

### 3.1. Devise (Authentication)

Skip all the default routes generated by devise on `routes.rb` and create custom controllers and views according to the requirements.

#### `routes.rb`

```ruby
devise_for :users, skip: :all
resource :sign_up, only: %i(new create), path_names: { new: '' }
resource :session, only: %i(new create destroy), path: 'login', path_names: { new: '' }
resource :confirmation, only: %i(new create show)
resource :password, only: %i(new create edit update)
```

#### Sessions Controller example: `app/controllers/sessions_controller.rb`

```ruby
class SessionsController < ApplicationController

  prepend_before_action :allow_params_authentication!, only: :create
  prepend_before_action :require_no_authentication, only: %i(new create)

  def new
    @user = User.new
    @user.clean_up_passwords
  end

  def create
    @user = authenticate_user!(recall: 'sessions#new')
    sign_in(@user)

    redirect_to sign_up_path, notice: t('.ok')
  end

  def destroy
    sign_out(:user)

    redirect_to new_session_path, notice: t('.ok')
  end

end
```

#### New session view example: `app/views/sessions/new.html.erb`

```erb
<div class="box">
  <hgroup class="login__header">
    <h1><%= t('.title') %></h1>
  </hgroup>

  <%= simple_form_for(@user,
                    url: session_path,
                    html: { class: 'login__form' }) do |form| %>
    <%= form.input :email %>
    <%= form.input :password %>
    <%= form.submit t('.submit'), class: 'btn-primary is-block' %>
    <p class="login__link">
      <%= link_to 'Forgot Password?', new_password_path, class: 'link--secondary'%>
    </p>
  <% end%>
</div>
```

### 3.2. Testing

We use the [Rspec](https://github.com/rspec/rspec) testing framework and usually we write these kind of tests:

* Unit tests for models, commands, jobs.
* System tests for integration.
* Request specs for APIs.
* Avoid controller tests: controllers functionality is already covered by integration specs.

### 3.2.1. Testing Best Practices

#### 3.2.1.1 Use let

When you have to assign a variable to test, instead of using a before each block, use let. It is memoized when used multiple times in one example, but not across examples.

```ruby
describe User do
  let(:user) { User.new(name: 'Rocky Balboa') }

  it 'has a name' do
    expect(user.name).to_not be_nil
  end
end
```

#### 3.2.1.2 Use factories

Use [factory\_bot](https://github.com/thoughtbot/factory_bot) to reduce the verbosity when working with models.

**`spec/factories/user.rb`**

```ruby
FactoryBot.define do
  name { 'Rocky Balboa '}
  age { 30 }
  active { true }
  role { :engineer }
end
```

**Using the factory**

```ruby
user = FactoryBot.create(:user, name: 'John Rambo') # The rest of attributes are already set
```

#### 3.2.1.3 Describe Methods

When testing a method, create a describe block with the name of the method and place the specs inside. Use "." as prefix for class methods and "#" as prefix for instance methods.

```ruby
describe ".authenticate" do
  it 'returns true when the user is active' { ... }
  it 'returns false when the user is deleted' { ... }
end

describe "#generate_export" do
  it 'returns an empty array when there are not users' { ... }
  it 'returns the list of active users' { ... }
end
```

## 4. Gems

* General
  * [Keynote](https://github.com/rf-/keynote) for presenters.
  * [Simple form](https://github.com/heartcombo/simple_form) for form generation.
  * [Dotenv + dotenv-rails](https://github.com/bkeepers/dotenv) for environment variables.
  * [Activeadmin](https://github.com/activeadmin/activeadmin) for admin panels.
  * [Devise](https://github.com/heartcombo/devise) for authentication.
  * [Sidekiq](https://github.com/mperham/sidekiq) for background jobs.
  * [Sidekiq-cron](https://github.com/ondrejbartas/sidekiq-cron) for scheduled jobs.
  * [Sidekiq-failures](https://github.com/mhfs/sidekiq-failures) for error tracking in background jobs.
  * [Shrine](https://github.com/shrinerb/shrine) for file uploads.
  * [Http (http-rb)](https://github.com/httprb/http) for http calls.
  * [Friendly\_id](https://github.com/norman/friendly_id) for slugged url generation.
  * [Kaminari](https://github.com/kaminari/kaminari) for pagination.
  * [Jbuilder](https://github.com/rails/jbuilder) for JSON API responses.
  * [Pundit](https://github.com/varvet/pundit) for authorization.
* Testing:
  * [Rspec](https://github.com/rspec/rspec-rails) testing framework.
  * [FactoryBot](https://github.com/thoughtbot/factory_bot) for factories.
  * [Webmock (not VCR)](https://github.com/bblimke/webmock) to mock external HTTP requests.
  * [Capybara](https://github.com/teamcapybara/capybara) for integration tests.
* Dev
  * [Better\_errors](https://github.com/BetterErrors/better_errors) for error enhancements.
  * [Pry + pry-rails](https://github.com/pry/pry) for a better console.
  * [Pry-byebug](https://github.com/deivid-rodriguez/pry-byebug) for console debugging.
  * [Bullet](https://github.com/flyerhzm/bullet) to detect N+1 queries.
  * [PgHero](https://github.com/ankane/pghero) for database insights.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://handbook.marsbased.com/our-development-guides/ruby-guidelines.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
