@radanskoric Hi
You’ve got several questions. Let me answer one-by-one.
Adding virtual attributes
The automatic mapping of database columns with entities attributes saves you from manually maintaining the schema.
Let’s say your columns are:
CREATE TABLE users (
id integer NOT NULL,
encrypted_password text,
created_at timestamp without time zone,
updated_at timestamp without time zone
);
Automatically, User
will have id
, encrypted_password
, created_at
, and updated_at
attributes.
Any change to the schema automatic schema must be added manually.
class User < Hanami::Entity
attributes do
attribute :id, Types::Int
attribute :password, Types::String
attribute :encrypted_password, Types::String
attribute :created_at, Types::Time
attribute :updated_at, Types::Time
end
end
Please note: when Entity’s manual schema is used, it won’t reflect the changes from the database schema. Any new column must be manually added here.
Dealing with passwords
The reason why User
has both password
and encrypted_password
is due to an old solution in the Rails community, that all the other new libraries cargo culted.
You can name the database column :password
and prepare the data before to pass it to the repository.
# apps/web/controllers/signups/create.rb
module Web::Controllers::Signups
class Create
include Web::Action
def call(params)
data = params[:signup].dup
data[:password] = Password.generate(data[:password])
user = UserRepository.new.create(data)
# ...
end
end
end
What’s the implementation of Password
? Here it is:
# lib/bookshelf/password.rb
require 'bcrypt'
class Password
DEFAULT_COST = BCrypt::Engine.cost
def self.generate(input, cost: DEFAULT_COST)
BCrypt::Password.create(input, cost: cost)
end
def initialize(encrypted_password)
@password = BCrypt::Password.new(encrypted_password)
end
def ==(other)
@password == other
end
end
Then User
can override #password
with a custom implementation:
# lib/bookshelf/entities/user.rb
class User < Hanami::Entity
def password
Password.new(super)
end
end
If you want to compare stored password with a login attempt:
user.password
# => #<Password:0x007fa3e8d89d50 @password="$2a$10$.bbV0QtW7.5JJSdEef4gM.xoF4NilOGh5J.TVbq1.o5OgpcQeO0cq">
user.password == "123"
# => true
user.password == "foo"
# => false
This eliminates the need of dealing with two attributes.
Repository#create vs Entity#new
For a repository, an entity is the result of an operation. When you create a new user, it returns an entity. When you find an entity, it returns one.
On the other hand, the input is always data. When you receive input from a form, it’s data. Instantiating an entity would be unnecessary ceremony.
We encourage to manipulate data after validations and before to persist it. We’re working on a way to standardize this process.