-
Notifications
You must be signed in to change notification settings - Fork 0
Abstract Chain

Model-View-Controller (MVC) now is a sign of bad taste. Everybody who built more or less serious production web-application know that controllers become to be fat. Splitting controllers by context can a bit improve the situation but it's a visual trick, it still looks like:
class UserController
def index
# ...
end
def show
# ...
end
def update
# ...
end
# ...
endIt roughly violates one of main OOP rules - Single Responsibility Principle. Somebody can say "controller is only intended to control the process of request handling", but it's too large responsibility. Actually it's responsible for creating, updating and deleting models, showing views, services invocation and so on. And it's an ideal picture. Usual situation is when domain code is located in controllers. Dandy replaces regular controllers with a chain of atomic actions. It doesn't protect you to use bad practices but allows to easily separate [controller-]actions. Every action is a simple brick:
class ShowUsers
def call
# ...
end
end
class UpdateUser
def call
# ...
end
endA route can represent much more complicated logic containing several steps. For that purpose Dandy provides an ability to chain actions:
-> user@load_user -> update_user -> notify_admin -> log_action
Every of enumerated above actions are classes respond to method "call". Each action should be as small as it possible: ideally they should do only one thing.
An action can return a result using "@" operator. The value declared by "@" can be used by the next actions. For example:
-> user@load_user -> update_user
class LoadUser
def initialize(id)
@id = id
end
def call
@user_repo.find(@id)
end
endAction "load_user" automatically registers "user" object in DI container. You can easily use it in "update_user":
class UpdateUser
def initialize(user, dandy_data)
@user = user
@data = dandy_data
end
def call
@user.update(@data)
end
endYou don't need to create an action just for a trivial delegation. Let's say we have User entity:
class User
...
def full_name
"#{first_name} #{last_name}"
end
endyou can easily access members of registered objects ("@") directly in route definition:
/user/what_is_my_full_name ->
GET -> user@load_current_user -> user.full_name
You can use a parameters for such calls. They cannot be declared directly in route definition in order to keep it clean. But all you need - just enumerate desired parameters in method signature, Dandy will "inject" required dependencies into the method:
/user/$id/change_role ->
PATCH -> user@load_user -> role@load_role -> user.change_role
class User
...
def change_role(role)
@role = role
end
endAs described above you can inject request parameters directly to an action. Also you can access other request values like query parameters, form data, etc.
dandy_params - is a params of the request like user/1/post/12 represented as Ruby hash. Usage example:
class LoadPost
def initialize(post_repo, dandy_params)
@dandy_params = dandy_params
@post_repo = post_repo
end
def call
@post_repo.get_by_id(@dandy_params[:id])
end
enddandy_data - is a JSON-object sent from client in request body and represented as Ruby hash. Usage example:
class UpdatePost
def initialize(post, dandy_data)
@post = post
@dandy_data = dandy_data
end
def call
@post.update(@dandy_data)
end
enddandy_query - is a query of the request like ?user_id=1&post_id=12 represented as Ruby hash.
class FindPosts
def initialize(post_repo, dandy_query)
@dandy_query = dandy_query
@post_repo = post_repo
end
def call
@post_repo.find_posts(type: dandy_query[:type])
end
enddandy_files - is an array of files extracted from multipart data.
class ImportPost
def initialize(post_repo, dandy_files, current_user)
@dandy_files = dandy_files
@post_repo = post_repo
@current_user = current_user
end
def call
post = extracted_post.merge(user: @current_user)
@post_repo.create(post)
end
private
def extracted_post
json = @dandy_files[0][:tempfile].read
...
end
endIf your frontend application uses camelCase keys then Dandy converts them automatically to snake_case. In example:
You send POST request with data:
{
"authorName": "Author Name"
}In Dandy action you receive a hash:
{:author_name => 'Author Name'}By default Dandy sends back a JSON in snake_case. If you wanna receive JSON in camelCase you need specify
the format in HTTP headers. For that purpose just add header 'Keys-Format': 'camel' to your request.
You should explicitly specify header 'Accept' for your requests. For JS-applications it should be 'Accept': 'application/json'.