Extend model with context that contains validations

Imagine that you need conditional validation and you can’t use ReForm or other form objects while extending third-party logic.
Then you can add custom context to model instance:

client = Client.find(id)
client.extend(Client::RegistrationContext)

module Client::RegistrationContext
  def self.extended(model)
    class << model
      validates :terms_of_service, acceptance: true
    end
  end

  attr_accessor :terms_of_service
end

`Validates` method is not available in the module context so we need to call it in the model context.

 

Extract value from resulting array to a method

For example, we have method output like:

[false, 'Error'] or
[true, result]

We can use it this way:

def action
  passed, result = call_some_method

  if passed
    do_calculations
  else
    render json: { error: result, status: 400 }
  end
end

And inside `do_calculations` we can access result by calling method `result`:

def do_calculations
  clients: method_1,
  schedules: method_2
end

private

def method_1
  ::CallSomeService.new(client, result).call
end

def method_2
  ::CallAnotherService.new(client, result).call
end

memoize def result
  call_some_method.yield_self { |_, result| result }
end

memoize def call_some_method
  # ...
end
 

Sandi Metz best practise advices about application design

1) If you do design too early, you’ll waste your efforts.

2) If you never do design, your code will become a painful mess.

3) A time will come when investing in design will save you money.

4) Simple procedures require little design and are cheap to maintain.

5) Procedures become more complex over time, and more expensive to maintain.

6) Object-oriented code is more cost-effective than complex procedural code.

7) The procedures that are most important to your domain change more than those that are incidental to your domain.

8) The procedures that are important to your domain increase in complexity faster than other code.

9) It’s difficult to be aware of the exact moment when your application crosses the design payoff line.

10) You become aware that you have passed the design payoff line because velocity slows and suffering increases.

11) The most important code will be the most out-of-control by the time you realize you’ve passed the design payoff line.

12) Moderately complex procedures are easy to convert to OO.

13) Extremely complex procedures are more difficult to convert to OO.

14) Your attempts to convert moderately complicated procedures to OO generally succeed.

15) Your attempts to convert extremely complicated procedures to OO often fail.

 

Clean way of initializing service objects in ruby

# service object class
class FancyObject
  attr_reader :entity

  def self.perform(params)
    new(params).perform
  end

  def perform
    ApplicationRecord.transaction do
      # entity processing logic here

      entity.save
      entity
    end
  end

  private

  def initialize(params)
    @entity   = Entity.new(params)
  end
end

# controller:
@entity = FancyObject.perform(entity_params)

if @entity.valid?
  #...
end
 

Adding postgres index with condition.

For example, If you need only 1 admin on each project.

class AddUsers < ActiveRecord::Migration[5.1]
  def change
	create_table :users do |t|
      t.integer :project_id, null: false
      t.string :name, null: false
      t.integer :role, null: false

      t.timestamps
    end

    reversible do |direction|
	  direction.up do
        # Partial unique index
        execute <<-SQL
          CREATE UNIQUE INDEX unique_user_role_in_project ON users (project_id, role) WHERE role = 'admin';
        SQL
      end

      direction.down do
        # Remove unique index
        execute <<-SQL
          DROP INDEX IF EXISTS unique_user_role_in_project;
        SQL
      end
    end
  end
end
 

Slim template data attributes for a tag example

div*{'data-toggle'=> 'modal', 'data-target'=> "#approval_modal_#{keyword.id}"}
 

Using merge on join for records filtering

# Returns all the accounts that have unread messages.
  def self.with_unread_messages
    joins(:messages).merge( Message.unread )
  end