ApplicationController Private method's variable rspec testing - ruby-on-rails

I have made this code in ApplicationController. This is a method which runs at the start of any method to run. I want to test this method with rspec and want to know that the gender is giving right output or wrong.
this is the route of the code
get ':sex/names/:name_kanji', to: 'names#show'
And this is the application controller:
class ApplicationController < ActionController::Base
before_action :check_sex
private
def check_sex
#header_color = nil
#header_nav_hash = {'other' => nil, 'boy' => nil, 'girl' => nil}
#page_scoop = params[:sex].presence || 'other'
unless #page_scoop.match(/^(boy|girl|other)$/)
render_404
end
if #page_scoop == "boy" || #page_scoop == "girl"
#gender_base = #page_scoop
end
#header_nav_hash[#page_scoop] = 'is-active'
#header_color = #page_scoop == 'boy' ? 'is-info' : 'is-danger' if #page_scoop != 'other'
end
def render_404
render template: 'errors/error_404' , status: 404 , layout: 'application' , content_type: 'text/html'
end
def render_500
render template: 'errors/error_500' , status: 500 , layout: 'application' , content_type: 'text/html'
end
end

I would inspect the instance variables that being set in the before_action. It would look something like this:
describe 'on GET to :show' do
before do
get :show, params: { <valid params here> }
end
it 'sets #page_scoop' do
expect(assigns(:page_scoop)).to eq 'boy'
end
end

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

Rails 5.1 API - index method with filter, pagination and scopes - how to simplify

I have an index method in a Rails API controller that is quite horrendous, as you can see below.
I am sure there is a more Ruby or Rails way to write this.
The action supports paging and filtering (by a filter= query parameter) and also can supply a customer-id to restrict what is returned to only proposals relevant to the provided customer.
I wonder if maybe I should separate the customer functionality to a separate endpoint? (eg. customers/:id/proposals). Of course that endpoint would also need to support paging and filter, so I think I might not end up with DRY code. Is there a way (like with Concerns) that I could make this index code simpler (ie. without all the if...then...else)?
def index
if params[:page].present?
page = params[:page]
if params[:filter].present?
if params[:customer_id].present?
#proposals = current_user.retailer.proposals.customer(params[:customer_id]).search(params.slice(:filter)).page(page)
else
#proposals = current_user.retailer.proposals.search(params.slice(:filter)).page(page)
end
else
if params[:customer_id].present?
#proposals = current_user.retailer.proposals.customer(params[:customer_id]).page(page)
else
#proposals = current_user.retailer.proposals.page(page)
end
end
render json: #proposals, root: 'proposals', meta: pagination_dict(#proposals)
else
render status: :bad_request, json: { message: "Please supply page parameter" }
end
end
Here are the Proposal model scopes:
default_scope { order("updated_at DESC") }
scope :filter, -> (term) { where("lower(first_name) || ' ' || lower(last_name) || ' ' || lower(email) LIKE ? OR qd_number::text LIKE ?", "%#{term.downcase}%", "%#{term}%") }
scope :customer, -> (customer_id) { where customer_id: customer_id }
I would start with something like this:
def index
if params[:page].present?
#proposals = current_user.retailer.proposals.page(params[:page])
#proposals = #proposals.customer(params[:customer_id]) if params[:customer_id].present?
#proposals = #proposals.search(params.slice(:filter)) if params[:filter].present?
render json: #proposals, root: 'proposals', meta: pagination_dict(#proposals)
else
render status: :bad_request, json: { message: "Please supply page parameter" }
end
end
Furthermore you might want to handle the error in a before_action:
before_action :check_required_parameters, only: :index
def index
#proposals = current_user.retailer.proposals.page(params[:page])
#proposals = #proposals.customer(params[:customer_id]) if params[:customer_id].present?
#proposals = #proposals.search(params.slice(:filter)) if params[:filter].present?
render json: #proposals, root: 'proposals', meta: pagination_dict(#proposals)
end
private
def check_required_parameters
return if params[:page].present?
render status: :bad_request, json: { message: "Please supply page parameter" }
end
Or you might want to change your scopes to handle blank values:
# in the model
scope :filter, -> (term) { where("lower(first_name) || ' ' || lower(last_name) || ' ' || lower(email) LIKE ? OR qd_number::text LIKE ?", "%#{term.downcase}%", "%#{term}%") if term.present? }
scope :customer, -> (customer_id) { where(customer_id: customer_id) if customer_id.present? }
# in the controller
def index
if params[:page].present?
#proposals = current_user.retailer.proposals
.customer(params[:customer_id])
.search(params.slice(:filter))
.page(params[:page])
render json: #proposals, root: 'proposals', meta: pagination_dict(#proposals)
else
render status: :bad_request, json: { message: "Please supply page parameter" }
end
end

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

Update value with first_or_create in rails

I have a table 'Likes' with columns business_id, user_id and liked(0,1) and a function 'change_like_status'.
Now on every function call, If the value is 1 then set it to 0 (or vice versa) and if record doesn't exists then create one with value 1.
The first_or_create method is working just fine but how can i toggle value of column 'liked' while using this method?
Here is my function:
def change_like_status
if current_user.present?
status = Like.where("business_id = ? AND user_id = ?",params['id'],current_user.id).first_or_create(:business_id => params['id'],:user_id => current_user.id,:liked => '1')
abort status.inspect
else
return render :json => {:status => false,:msg=>"You need to sign in before performing this action."}
end
end
In you controller, make the changes
def change_like_status
if current_user
status = Like.create_or_change_status(params[:id], current_user.id)
else
return render json: { status: false, msg: "You need to sign in before performing this action." }
end
end
In your model like.rb file, add a method
def self.create_or_change_status(business_id, user_id)
status = where(business_id: business_id, user_id: user_id).first
if status.nil?
status = create({business_id: business_id, user_id: user_id, liked: 1})
else
status.update_attributes(liked: !status.liked)
end
status
end
def change_like_status
if current_user
current_user.likes.find_by(business_id: params[:id]).switch_status!
else
return render json: { status: false, msg: "You need to sign in before performing this action." }
end
end
class Like
def switch_status!
self.update_column :liked, !liked
end
end
other approach should be something like that
class Like
def switch_status!
self.update_column :liked, !liked
end
end
class User
def likes id
likes_for_business id
end
def likes_for_business(id)
likes.find_by(business_id: id) || likes.create(:business_id: id, liked: true)
end
end
# controller
current_user.likes(params[:id]).switch_status!

If current time is 1 or more days earlier than starts_at

I have an exam starts_at field, and what I want to do is, if current date is 1 day or more before exam starts_at I want to redirect to somewhere else. And if current date is the same day as when the exam starts_at I want to redirect to the exam page....for now I just want to get the if statement correct, I will put the redirect later.
Here is my controller.
Student Session Controller
class StudentSessionsController < ApplicationController
before_action :set_student_session
before_filter :config_opentok, except: :update
before_action :try_authenticate_user!, except: :mobile
before_action :check_compatability, except: :mobile
def show
#session = #student_session.session
#session_id = #session.session_id
#token = #opentok.generate_token #session_id, :data => "#{#student_session.id}"
# If Time.now is 1 or more days before exam starts_at show message
if (#session.exam.starts_at =< Time.now)
render :text => "OK"
else
render :text => "Not ok"
end
if #student_session.student.present?
#UserMailer.mobile_link(current_user.email, current_user.name, #student_session).deliver
else
#UserMailer.mobile_link(#student_session.email, #student_session.email, #student_session).deliver
end
ua = UserAgent.parse(request.user_agent)
#student_session.operating_system = ua.os
#student_session.browser = ua.browser
#student_session.browser_version = ua.version.to_s
#student_session.save
render layout: "application_no_header"
end
def mobile
#session = #student_session.session
#session_id = #session.session_id
#token = #opentok.generate_token #session_id, :data => "#{#student_session.id}_mobile"
render layout: false
end
def update
respond_to do |format|
if #student_session.update(student_session_params)
format.js
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_student_session
#student_session = StudentSession.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def student_session_params
params.require(:student_session).permit(:session_status, :publisher_status, :proctor_status, :mobile_status)
end
def config_opentok
#opentok ||= OpenTok::OpenTok.new APP_CONFIG['opentok']['api_key'], APP_CONFIG['opentok']['secret']
end
def try_authenticate_user!
if #student_session.student.present?
authenticate_user!
end
end
def check_compatability
user_agent = UserAgent.parse(request.user_agent)
# http://tokbox.com/opentok/requirements/
unless (user_agent.browser == 'Chrome' and user_agent.version.to_a.first >= 23) or
(user_agent.browser == 'Firefox' and user_agent.version.to_a.first >= 22)
redirect_to '/browser'
end
end
end
I'd suggest you use:
if #session.exam.starts_at.to_date == Date.today
# go to exam
elsif #session.exam.starts_at.to_date < Date.today
# go to place before the exam
else
# go to place after the exam
end
As you're using rails you can do
if (Time.now >= (#session.exam.starts_at - 1.day) )
render :text => "OK"
else
render :text => "Not ok"
end
if ((#session.exam.starts_at - Time.now).to_i / 1.day) >= 1
render :text => "OK"
else
render :text => "Not ok"
end

Resources