Best practices for Ruby on Rails service - ruby-on-rails

I'm writing some mobile otp validation service and below is my service class
require 'nexmo'
class NexmoServices
def initialize api_key = nil, api_secret = nil, opts = {}
api_key = api_key || Rails.application.secrets.nexmo_api_key
api_secret = api_secret || Rails.application.secrets.nexmo_secret_key
#nexmo_client = Nexmo::Client.new(
api_key: api_key,
api_secret: api_secret,
code_length: 6
)
#brand = 'CryptoShop'
end
def send_verification_code opts
#nexmo_client.verify.request(number: opts[:number], brand: #brand)
end
def check_verification_code opts
#nexmo_client.verify.check(request_id: opts[:request_id], code: opts[:verification_code])
end
def cancel_verification_code opts
#nexmo_client.verify.cancel(opts[:request_id])
end
end
in the controller i'm calling the service methods like below
class NexmoController < ApplicationController
def send_verification_code
response = NexmoServices.new.send_verification_code params[:nexmo]
if response.status == '0'
render json: response.request_id.to_json
else
render json: response.error_text.to_json
end
end
def cancel_verification_code
response = NexmoServices.new.cancel_verification_code params[:nexmo]
if response.status == '0'
render json: response.to_json
else
render json: response.error_text.to_json
end
end
end
I have read that usually there will be call method inside service class and controller will call that. service method call will take care of the rest.
My case im instantiating service objects for all the methods if you see my controller(NexmoService.new).
is it correct??? I want to know the best practise must be followed in this scenario.
Thanks,
Ajith

Related

Rails 5 manage result from monads

I've got Rails 5 app with dry-monads on board. Monads are used to create the Appointment object inside create action in AppointmentsController. They return Success or Failure in the last step with below structure:
# services/appointments/create.rb
(...)
def call
Success(appointment_params: appointment_params)
(...)
.bind(method(:save_appointment))
end
private
def save_appointment(appointment)
if appointment.save
Success(appointment)
else
Failure(failure_appointments: appointment, appointments_errors: appointment.errors.full_messages)
end
end
After each action (success or failure) I want to send an email and display the corresponding json in AppointmentsController:
class Api::AppointmentsController < ApplicationController
def create
succeeded_appointments = []
failure_appointments = []
appointments_errors = []
batch_create_appointments_params[:_json].each do |appointment_params|
appointment = ::Appointments::Create.new(appointment_params).call
if appointment.success?
succeeded_appointments << appointment.value!
else
failure_appointments << appointment.failure[:failure_appointments] &&
appointments_errors << appointment.failure[:appointments_errors]
end
end
if failure_appointments.any?
AppointmentMailer.failed_mail(email, failure_appointments.size, appointments_errors).deliver_now
render json: {
error: appointments_errors.join(', '),
}, status: :bad_request
elsif succeeded_appointments.any?
AppointmentMailer.success_mail(email, succeeded_appointments.size).deliver_now
render json: {
success: succeeded_appointments.map do |appointment|
appointment.as_json(include: %i[car customer work_orders])
end,
}
end
end
I wonder if there is a better, faster way to record these errors than declaring 3 different empty arrays (succeeded_appointments, failure_appointments, appointments_errors) like at the beginning of create action? so far the create action looks heavy.
Create a separate service object for bulk creation:
# services/appointments/bulk_create.rb
class Appointments::BulkCreate
def initialize(bulk_params)
#bulk_params = bulk_params
end
def call
if failed_results.any?
AppointmentMailer.failed_mail(email, failed_results_errors.size, failed_results_errors).deliver_now
Failure(failed_results_errors.join(', '))
else
AppointmentMailer.success_mail(email, success_appointments.size).deliver_now
Success(success_appointments)
end
end
private
attr_reader :bulk_params
def failed_results
results.select(&:failure?)
end
def success_results
results.select(&:success?)
end
def success_appointments
#success_appointments ||= success_results.map do |appointment|
appointment.as_json(include: %i[car customer work_orders])
end
end
def failed_results_errors
#failed_results_errors ||= failed_results.map do |failed_result|
failed_result.failure[:appointments_errors]
end
end
def results
#results ||= bulk_params.map do |appointment_params|
::Appointments::Create.new(appointment_params).call
end
end
end
Then your controller will look like this:
class Api::AppointmentsController < ApplicationController
def create
result = ::Appointments::BulkCreate.new(batch_create_appointments_params[:_json]).call
if result.success?
render json: { success: result.value! }, status: :ok
else
render json: { error: result.failure }, status: :bad_request
end
end
end

Problem with current_user in GrapghqlController

I'm trying to assign a value to current_user in GraphqlController but it's not working.
this is my code in graphql_controller.rb
class GraphqlController < ApplicationController
# If accessing from outside this domain, nullify the session
# This allows for outside API access while preventing CSRF attacks,
# but you'll have to authenticate your user separately
# protect_from_forgery with: :null_session
def execute
variables = ensure_hash(params[:variables])
query = params[:query]
operation_name = params[:operationName]
context = {
# Query context goes here, for example:
session: session,
current_user: current_user
}
result = GraphqlSimpleLoginAppSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
render json: result
rescue => e
raise e unless Rails.env.development?
handle_error_in_development e
end
private
def current_user
return unless session[:token]
AuthToken.verify(session[:token])
end
# Handle form data, JSON body, or a blank value
def ensure_hash(ambiguous_param)
case ambiguous_param
when String
if ambiguous_param.present?
ensure_hash(JSON.parse(ambiguous_param))
else
{}
end
when Hash, ActionController::Parameters
ambiguous_param
when nil
{}
else
raise ArgumentError, "Unexpected parameter: #{ambiguous_param}"
end
end
def handle_error_in_development(e)
logger.error e.message
logger.error e.backtrace.join("\n")
render json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: 500
end
end
but current_user remains null. I don't know why. Please can you help me ?

How to send a get to a server and wait a get in my app in Ruby on Rails

I am using Zapier to search some information in google sheets. I used Webhocks to send a GET to his server with a JSON information. The response of GET is an "OK" and I can't custom this.
So, they will execute a task, find what a I want and return a value, but the response must be a GET in my server, and I don't know how to intercept this response in my route.
I'm trying to study Rails Rack to intercept de request in my app, but I don't know how to send the response to the event that sent the first GET.
How is my middleware:
class DeltaLogger
def initialize app
#app = app
end
def call env
Rails.logger.debug "#{env['QUERY_STRING']}"
#status, #headers, #response = #app.call(env)
[#status, #headers, #response]
end
end
Thanks!
Example
So, to get the value returned from Zapier, I created two routes and a global class cache.
class Zapier
require 'httparty'
def initialize
#answer = ""
#id = 0
end
def request(uri, task)
last_event = Event.last
puts last_event.inspect
if last_event.nil?
last_id = 0
else
last_id = last_event.event_id
end
event_id = last_id + 1
Event.find_or_create_by(event_id: event_id)
result = HTTParty.post(uri.to_str,
:body => {id: event_id, task: task}.to_json,
:headers => {'content-Type' => 'application/json'})
#answer = ""
#id = event_id
end
def response(event_id, value)
if event_id != #id
#answer = ""
else
#answer = value
end
end
def get_answer
#answer
end
end
And my controller:
class ZapierEventsController < ApplicationController
require 'zapier_class'
before_action :get_task, only: [:get]
before_action :get_response, only: [:set]
##zapier ||= Zapier.new
def get
##zapier.request('https://hooks.zapier.com',#task)
sleep 10 #Wait for response
#value = ##zapier.get_answer
render json: { 'value': #value }, status:
end
def set
##zapier.response(#id, #value)
render json: { 'status': 'ok' }, status: 200
end
def get_task
#task = params["task"]
end
def get_response
#id = Integer(params["id"])
#value = params["value"]
end
end
Now i have to make a Task Mananger

How to render a jsonapi-resources response in an custom controller action?

I have implemented my own object creation logic by overriding the create action in a JSONAPI::ResourceController controller.
After successful creation, I want to render the created object representation.
How to render this automatically generated JSON API response, using the jsonapi-resources gem?
Calling the super method does also trigger the default resource creation logic, so this does not work out for me.
class Api::V1::TransactionsController < JSONAPI::ResourceController
def create
#transaction = Transaction.create_from_api_request(request.headers, params)
# render automatic generated JSON API response (object representation)
end
end
You could do something like this:
class UsersController < JSONAPI::ResourceController
def create
user = create_user_from(request_params)
render json: serialize_user(user)
end
def serialize_user(user)
JSONAPI::ResourceSerializer
.new(UserResource)
.serialize_to_hash(UserResource.new(user, nil))
end
end
this way you will get a json response that is compliant with Jsonapi standards
render json: JSON.pretty_generate( JSON.parse #transaction )
def render_json
result =
begin
block_given? ? { success: true, data: yield } : { success: true }
rescue => e
json_error_response(e)
end
render json: result.to_json
end
def json_error_response(e)
Rails.logger.error(e.message)
response = { success: false, errors: e.message }
render json: response.to_json
end
render_json { values }

How to refactor wrapper using singleton to set up session?

module Asterisk
class Client
include HTTParty
base_uri 'https://asterisk.dev/'
def initialize(session_key = nil)
#session_key = session_key
end
def get_session_key(login, password)
self.class.put('/api/auth', query: { login: login, password: password })
end
def get_contacts
self.class.get("/api/#{#session_key}/contacts")
end
def get_contact(id)
self.class.get("/api/#{#session_key}/contact/#{id}")
end
def create_contact
self.class.put("/api/#{#session_key}/create")
end
def logout
self.class.delete("/api/#{#session_key}/logout")
end
end
end
Right now it works like below
session_key = Asterisk::Client.new.get_session_key('login', 'pass')
client = Asterisk::Client.new(session_key)
client.get_contacts
I would like to get and set session key using singleton. How to do that?
module Asterisk
class Client
include HTTParty
include Singleton
base_uri 'https://asterisk.dev/'
attr_reader :last_session_update
private
def session_key
if !#session_key || session_refresh_needed?
#session_key = set_session_key
#last_session_update = Time.now
else
#session_key
end
end
def set_session_key
self.class.put('/api/auth', query: { login: login, password: password })
end
def password
#the way you get pass
end
def login
#the way you get login (ENV...)
end
def session_refresh_needed?
return true unless last_session_update
( Time.now - last_session_update) > 30.minute
end
end
end
It includes your issue with 30 minutes refresh.
Now call Asterisk::Client.instance

Resources