Rails 5 Scheduling App Find all shifts for a user - ruby

We are attempting to create an endpoint that allows for a user to view all of their personal shifts across all calendars. Currently I have the following syntax in the shifts controller:
def myschedule
#user = current_user
if #user
#shifts = Shift.where("end_time > ?", Time.current).order("start_time ASC").limit(100)
render "/shifts/index2.json", status: :ok
else
render json: ('You do not have access to these shifts'), status: :unauthorized
end
end
This as expected is returning the first 100 shifts across all calendars, but for all users. What would be the best direction to search for a way to limit the shifts to just the ones for a specified user. I would appreciate some guidance in the right direction. The json rendered is an array of hashes.
As far as relations go on the model level:
a shift has_many users, through usershifts
a user has_many shifts, through usershifts
both user and shift has_many usershifts
and usershift belongs_to both user and shift.
I attempted:
def myschedule
#user = current_user
if #user
#shifts = Shift.where("end_time > ?", Time.current).order("start_time ASC").limit(100).include?(#user)
render "/shifts/index2.json", status: :ok
else
render json: ('You do not have access to these shifts'), status: :unauthorized
end
end
Which returned undefined method map for false: Falseclass in my json view.
Here is the json view as well:
json.shifts #shifts do |shift|
json.shift_id shift.id
json.start_time shift.start_time
json.end_time shift.end_time
json.calendar_name shift.calendar.name
end
I also tried to throw a .joins(#user) to the end of #shifts, that returned an unknown class User for the json.
Also I attempted:
def myschedule
if current_user
#shifts = Shift.where("end_time > ?", Time.current).order("start_time ASC").limit(100).where(:user_id => params[:user_id])
render "/shifts/index2.json", status: :ok
else
render json: ('You do not have access to these shifts'), status: :unauthorized
end
end
which gave the following error in server log
ActionView::Template::Error (PG::UndefinedColumn: ERROR: column
shifts.user_id does not exist
LINE 1: ...ERE (end_time > '2018-10-13 14:52:02.693352') AND "shifts"."

What would be the best direction to search for a way to limit the
shifts to just the ones for a specified user
This should work
#shifts = #user.shifts.where("end_time > ?", Time.current).order("start_time ASC").limit(100)

Related

How to test Stripe's invoice_pdf property when it keeps changing?

In my Rails 6 app I have a very simple controller that displays download links to a user's Stripe invoice PDFs:
class ReceiptsController < ApplicationController
before_action :signed_in_user
def index
receipts = current_account.receipts
end
def show
receipt = current_account.receipts.find(params[:id])
stripe_invoice = Stripe::Invoice.retrieve(receipt.stripe_invoice_id)
redirect_to stripe_invoice.invoice_pdf
end
end
Since Stripe doesn't provide permanent invoice URLs (please correct me if I am wrong), I am storing each invoice's Stripe ID in the database and then use that ID to lookup the current URL to the invoice PDF from the Stripe API.
The problem is that this works most of the time but not all the time. The spec that I created for the controller show action fails in about 20 % of cases because the two URLs do not match:
describe ReceiptsController, :type => :controller do
before :each do
#account = FactoryBot.create(:activated_account)
#user = #account.users.create(FactoryBot.attributes_for(:user))
sign_in(#user)
end
describe 'GET #show' do
# The implementation details of this block don't really matter
before :each do
Customers::FindOrCreate.call(#account)
stripe_subscription = Subscriptions::CreateRemote.call(#account,:payment_behavior => "default_incomplete")
#stripe_invoice = stripe_subscription.latest_invoice
#receipt = Receipts::Create.call(#stripe_invoice)
end
# This test fails in about 20 % of cases because the redirect does not go to #stripe_invoice.invoice_pdf but a slightly different URL
it "redirects to Stripe invoice PDF" do
get :show, :params => {:id => #receipt}
expect(response).to redirect_to #stripe_invoice.invoice_pdf
end
end
end
How can this be? Does the invoice_pdf property of a Stripe invoice change every few seconds? I've been trying to work this out for days now but can't get my head around it.
Addition:
This is a typical test failure that I get quite often:
Expected response to be a redirect to <https://pay.stripe.com/invoice/acct_105jfm2HzYSlmhv7/test_YWNjdF8xMDJqc20yS3pZUmxzaHc0LF9NMGZONnFzNUpPTjlObVprd0hvdGpIdWFUamJHTTVxLDQ3Njc3MDY30200oOxX3A1/pdf?s=ap> but was a redirect to <https://pay.stripe.com/invoice/acct_105jfm2HzYSlmhv7/test_YWNjdF8xMDJqc20yS3pZUmxzaHc0LF9NMGZONnFzNUpPTjlObVprd0hvdGpIdWFUamJHTTVxLDQ3Njc3MDY402001iYCSUbn/pdf?s=ap>.
Expected "https://pay.stripe.com/invoice/acct_105jfm2HzYSlmhv7/test_YWNjdF8xMDJqc20yS3pZUmxzaHc0LF9NMGZONnFzNUpPTjlObVprd0hvdGpIdWFUamJHTTVxLDQ3Njc3MDY30200oOxX3A1F/pdf?s=ap" to be === "https://pay.stripe.com/invoice/acct_105jfm2HzYSlmhv7/test_YWNjdF8xMDJqc20yS3pZUmxzaHc0LF9NMGZONnFzNUpPTjlObVprd0hvdGpIdWFUamJHTTVxLDQ3Njc3MDY402001iYCSUbn/pdf?s=ap".

Ruby on Rails: Having problems selecting correct parameters for a method depending on context the is method used

I am working on a application where Users can list their in-game items to trade with other Users. A user's profile url would be something like this:
/users/1/index
And their user listings profile would be something like
/users/1/listings/1
All other resources nested under users would be the same as the latter.
I am trying to implement a method that is called by a before_filter callback that checks to see if a user has blocked or is blocked by the user who owns the profile and respective nested resources such as ability to message them, view their listings etc. If either has blocked each other, then they redirected to the root page of the application. This is the method that I use for the before_filter:
def blocked_relationships
if blocked?
redirect_to :root
end
end
I used another method that checks the state of the relationships between the two users.
This is the method I found and worked on after some research courtesy of the Rails Recipes book:
def blocked?
Relationship.exists?(user_id: current_user.id, other_user_id: params[:user_id], status: "blocked") ||
Relationship.exists?(user_id: params[:user_id], other_user_id: current_user.id, status: "blocked")
end
The problem I have is that this method only works, for example, when User 1 is looking at User 2's items, messages, listings etc. because the url:
/users/2/listings [or items or etc]
will contain a params that makes reference to the user as params[:user_id]. params[:id] in this case and context will refer to the listings id.
BUT, if I am User 1 and I have blocked User 2 and visit User 2's profile, this method will not work because the url /users/2/index will use params[:id] to instead of params[:user_id].
I've been thinking about how to implement this in a DRY way but I can't seem to solve my problem other than doing something like this:
def blocked?
if params[:user_id].blank?
Relationship.exists?(user_id: current_user.id, other_user_id: params[:id], status: "blocked") ||
Relationship.exists?(user_id: params[:id], other_user_id: current_user.id, status: "blocked")
else
Relationship.exists?(user_id: current_user.id, other_user_id: params[:user_id], status: "blocked") ||
Relationship.exists?(user_id: params[:user_id], other_user_id: current_user.id, status: "blocked")
end
end
I also considered the possibility that I'm not even implementing my blocking feature correctly, but before I address that issue, I was wondering if anyone had any ideas on how to solve this problem. Any help or feedback would be greatly appreciated and I would be happy to add anymore information for clarification. Thanks!
Why not other_id = params[:user_id] || params[:id]? This is a way to override :id when :user_id is present.
About your blocking feature though, to me I'd like to see a user even if I've blocked them. I'd create a blocked_by_user_id field on the Relationship to see who did the blocking and only disallow the blocked party from seeing the user's profile.
You'd probably want to checkout authorization gems for rails like cancan or related (it's not my favorite but the most popular). However, you could handle it like this:
class User
has_many :relationships,
scope :accessible_by,
->(user) { where.not id: user.relationships.where(status: :blocked).pluck(:other_user_id) }
end
Then use the relationship User.accessible_by(current_user) on your controller instead of plainly User to retrieve resources. For example:
class UsersController < ApplicationController
def index
#users = User.accessible_by(current_user)
# bleh
end
def show
#user = User.accessible_by(current_user).find(params[:id])
# etc
end
end
When the resource is nested under a user you could do this:
class Users::PicturesController < UsersController
def index
#pictures = User.accessible_by(current_user)
.find(params[:user_id]).pictures
end
def show
#picture = User.accessible_by(current_user)
.find(params[:user_id]).pictures.find(params[:id])
end
end
When a user tries to access a resource that can't view, ActiveRecord::RecordNotFound will be raised, so you should handle it:
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNorFound, with: :rescue_not_found
private
def rescue_not_found
redirect_to root_path,
notice: 'You can\'t access that with your current priveleges. '
end
end

How to display payment gateway response messages in view - Rails 4, Active Merchant

I'm using Active Merchant with Stripe as the payment gateway. Everything works fine except that i don't know how to go about getting the gateway response error messages from Stripe (when a card is declined, invalid etc) to display on the checkout page to the user. I can get a StandardError to be raised that redirects to an error page with the response message but that's it.
ORDER MODEL
class Order < ActiveRecord::Base
has_many :order_products
has_many :products, through: :order_products
attr_accessor :card_number, :security_code, :card_expires_on
validate :validate_card, :on => :create
def validate_card
unless credit_card.valid?
credit_card.errors.full_messages.each do |message|
errors[:base] << message
end
end
end
def purchase(basket)
response = GATEWAY.purchase(Product.total_basket_price(basket)*100, credit_card, purchase_options)
unless response.success?
raise StandardError, response.message
end
end
def credit_card
#credit_card ||= ActiveMerchant::Billing::CreditCard.new(
:number => card_number,
:first_name => first_name,
:last_name => last_name,
:verification_value => security_code,
:month => card_expires_on.month,
:year => card_expires_on.year
)
end
def purchase_options
{
:billing_address => {
:address1 => address_1,
:address2 => address_2,
:city => city,
:country => country_code,
:zip => postal_code
}
}
end
end
ORDERS CONTROLLER
class OrdersController < ApplicationController
def create
#order = Order.new(order_params)
# #product = basket.find(params[:product_id])
basket.each do |item_id|
#order.order_products.build(product: Product.find(item_id))
end
if #order.save
if #order.purchase(basket)
render "show"
else
render "failure"
end
else
render "new"
end
end
Can anyone lend a hand, please??
Many Thanks
Easy peasy!
This is a simple matter of control flow. In Ruby, as in most languages, exceptions interrupt the normal program flow. As your code is written now, #purchase is raising an exception when it fails.
That's fine and a perfectly valid design decision. But the code interacting with #purchase is this:
if #order.purchase(basket)
render "show"
else
render "failure"
end
That code has no exception handling, so any exception will be caught by Rails, program flow will halt, and you'll get either a detailed error page (in development mode) or a generic 500 error page (in production mode).
Since you profess to be new to Ruby and Rails, a little code substitution might make this clearer:
# If #purchase is successful, it evaluates to true.
if true
render "show" # 'show' view is rendered as expected. Flow stops.
else
render "failure"
end
# If #purchase fails, it raises an exception.
if raise StandardError, response.message
# ^^^ Exception is raised, flow stops here.
render "show" # This code is never reached.
else # This code is never reached.
render "failure" # This code is never reached.
end
As I implied in the beginning, though, it's an easy fix once you know what the issue is. You can simply handle the exception with rescue. Where you currently have an if/else block, you can swap in an if block and a rescue block:
if #order.purchase(basket)
render 'show'
end
rescue => e
render 'failure'
There's room for improvement here depending on your needs—since you're raising and rescuing StandardError, for example, your can't easily distinguish between a network failure and a declined card—but it'll get you moving again.
After a lot of fiddling and help, the working solution was to search for an error key within the response params hash and if an error was present add the message to the object errors. Not particularly elegant but it now does what i want.
ORDER MODEL
def purchase(basket)
response = GATEWAY.purchase(Product.total_basket_price(basket)*100, credit_card, purchase_options)
if response.params.key?('error')
self.errors.add :base, response.message
false
else
true
end
end
ORDERS CONTROLLER
Also switched the order of the if statements in the controller so that def purchase(basket) runs first before the order is saved, allowing the error message(s) from the response to be caught and displayed.
if #order.purchase(basket)
if #order.save
render "show"
else
render "new"
end
else
render "new"
end
VIEW
<%= if #order.errors.any?
#order.errors[:base].to_sentence
end%>

Strong parameters and Nested Routes - Rails 4.0

I have no idea how this works in rails but I set up routes like this:
resources :users do
resources :api_keys
end
(User has_many: api_keys, api_key belongs_to: user)
So I then (since I only care about API Keys), created the following controller:
class ApiKeysController < ApplicationController
before_action :authenticate_user!
def index
#user = User.find(params[:user_id])
#api_key = User.apikeys
end
def create
#user = User.find(params[:user_id])
#api_key = ApiKey.new(create_new_api_key)
create_api_key(#api_key, #user)
end
def destroy
destroy_api_key
end
private
def create_new_api_key
params.require(:api_key).permit(user_attributes: [:id], :api_key)
end
end
Which states, authenticate user before every action, index fetches all api keys based on a user id. create is suppose to create an api key based on a user id, (note: create_api_key(#api_key, #user) just an abstracted method that states - if we saved, redirect to user_path with a message, if we failed, back to user path with a error message)
And destroy, well that just finds an api key, destroys it and redirects (again with the abstraction).
Whats the issue?
the create_new_api_key method. Its freaking out and saying:
syntax error, unexpected ')', expecting => (SyntaxError)
I thought this is how I pass in the user id ??
You need to change the order of the arguments passed in to permit to fix the syntax error:
def create_new_api_key
params.require(:api_key).permit(:api_key, user_attributes: [:id])
end

Generating JSON for Sinatra

I'm having an issue with passing the generated JSON notation of my object to my Sinatra application. The problem I have is twofold:
I have 2 classes that are mapped to a database using the Sequel gem. When they generate JSON it is ok and properly implemented.
I have a custom class called registration that maps one of the classes with an additional field. The goal is to generate JSON out of this and pass that JSON to the application using cucumber (test purpose)
The application code responsible for handling the request has the following function defined:
post '/users' do
begin
hash = JSON.parse(self.request.body.read)
registration = Registration.new.from_json(#request.body.read)
registration.user.country = Database::Alaplaya.get_country_by_iso_code(registration.user.country.iso_code)
return 400 unless(registration.is_valid?)
id = Database::Alaplaya.create_user(registration.user)
# If the registration failed in our system, return a page 400.
return 400 if id < 1
end
problem 1: I cannot use the params hash. It exists but is just an empty hash. Why?
problem 2: I cannot deserialize the JSON generated by the class itself. Why?
The registration class looks like this:
require 'json'
class Registration
attr_accessor :user, :project_id
def to_json(*a)
{
'json_class' => self.class.name,
'data' => [#user.to_json(*a), #project_id]
}.to_json(*a)
end
def self.json_create(o)
new(*o['data'])
end
# Creates a new instance of the class using the information provided in the
# hash. If a field is missing in the hash, nil will be assigned to that field
# instead.
def initialize(params = {})
#user = params[:user]
#project_id = params[:project_id]
end
# Returns a string representing the entire Registration.
def inspect
"#{#user.inspect} - #{#user.country.inspect} - #{#project_id}"
end
# Returns a boolean valid representing whether the Registration instance is
# considered valid for the API or not. True if the instance is considered
# valid; otherwise false.
def is_valid?
return false if #user.nil? || #project_id.nil?
return false if !#user.is_a?(User) || !#project_id.is_a?(Fixnum)
return false if !#user.is_valid?
true
end
end
I had to implement the methods to generate the JSON output correctly. When I run this in console I get the following output generated:
irb(main):004:0> r = Registration.new(:user => u, :project_id => 1)
=> new_login - nil - 1
irb(main):005:0> r.to_json
=> "{\"json_class\":\"Registration\",\"data\":[\"{\\\"json_class\\\":\\\"User\\\
",\\\"login\\\":\\\"new_login\\\"}\",1]}"
Which looks like valid JSON to me. However when I POST this to the application server and try to parse this, JSON complains that at least 2 octets are needed and refuses to deserialize the object.
If you're using Sequel as your ORM, try something like this:
In your model:
class Registration < Sequel::Model
many_to_one :user
many_to_one :project
plugin :json_serializer
end
The server:
before do
#data = JSON.parse(request.body.read) rescue {}
end
post '/users' do
#registration = Registration.new #data
if #registration.valid?
#registration.save
#registration.to_json #return a JSON representation of the resource
else
status 422 #proper status code for invalid input
#registration.errors.to_json
end
end
I think you may be overcomplicating your registration process. If the HTTP action is POST /users then why not create a user? Seems like creating a registration is overly complex. Unless your user already exists, in which case POST /users would be incorrect. If what you're really intending to do is add a user to to a project, then you should PUT /projects/:project_id/users/:user_id and the action would look something like this:
class User < Sequel::Model
many_to_many :projects
end
class Project < Sequel::Model
many_to_many :users
end
#make sure your db schema has a table called users_projects or projects_users
put '/projects/:project_id/users/:user_id' do
#find the project
#project = Project.find params[:project_id]
raise Sinatra::NotFound unless #project
#find the user
#user = Project.find params[:project_id]
raise Sinatra::NotFound unless #user
#add user to project's users collection
#project.add_user #user
#send a new representation of the parent resource back to the client
#i like to include the child resources as well
#json might look something like this
#{ 'name' : 'a project name', 'users' : ['/users/:user_id', '/users/:another_user_id'] }
#project.to_json
end

Resources