I built a webhook endpoint for a 3rd party API, but the issue I'm having is the webhook is failing to process some of the attributes that utilize arrays. I can't quite figure out why its not correctly updating/persisting the changes that the webhook is making in the system. How can I fix my webhook endpoint to allow changes to be made?
Error
Started POST "/gh_webhook" for ..... at 2020-04-06 10:58:02 -0400
Cannot render console from ....! Allowed networks: ..., ::1
Processing by GHController#gh_webhook as HTML
Parameters: {"Id"=>"459b58d7-5a9e", "ReportStatus"=>{"Id"=>"7eac420d", "Status"=>"New", "StatusDetails"=>"New", "CheckStatuses"=>[]}, "good_hire"=>{"Id"=>"459b58d7-5a9e", "ReportStatus"=>{"Id"=>"7eac420d", "Status"=>"New", "StatusDetails"=>"New", "CheckStatuses"=>[]}}}
Completed 500 Internal Server Error in 3ms (ActiveRecord: 0.0ms | Allocations: 1164)
NoMethodError (undefined method `[]' for nil:NilClass):
app/controllers/gh_controller.rb:7:in `gh_webhook'
GH webhook controller
class GHController < ApplicationController
skip_before_action :verify_authenticity_token
def gh_webhook
resp = JSON.parse(request.body.read)
report_id = resp["Id"]
candidate_first_name = resp["ReportStatus"]["Candidate"]["FirstName"]
candidate_last_name = resp["ReportStatus"]["Candidate"]["LastName"]
candidate_middle_name = resp["ReportStatus"]["Candidate"]["MiddleName"]
candidate_email = resp["ReportStatus"]["Candidate"]["Email"]
report_status = resp["ReportStatus"]["Status"]
report_status_details = resp["ReportStatus"]["Pending"]
report_adverse_action_status = resp["ReportStatus"]["AdverseActionStatus"]
report_viewer_url = resp["ReportStatus"]["ReportViewerUrl"]
candidate_url = resp["ReportStatus"]["CandidateUrl"]
required_report_actions = resp["ReportStatus"]["RequiredReportActions"]
check_statuses = resp["ReportStatus"]["CheckStatuses"]
sections_containing_alerts = resp["ReportStatus"]["SectionsContainingAlerts"]
background_check_report = BackgroundCheckReport.find_by_report_id(report_id) || BackgroundCheckReport.create(report_id: report_id)
background_check_report.update(candidate_first_name: candidate_first_name, candidate_last_name: candidate_last_name, candidate_middle_name: candidate_middle_name, candidate_email: candidate_email, report_status: report_status, report_viewer_url: report_viewer_url, candidate_url: candidate_url, report_status_details: report_status_details, sections_containing_alerts: sections_containing_alerts, check_statuses: check_statuses, required_report_actions: required_report_actions, adverse_action_status: report_adverse_action_status)
head :ok
end
end
schema
create_table "background_check_reports", force: :cascade do |t|
t.string "candidate_first_name"
t.string "candidate_last_name"
t.string "candidate_email"
t.string "report_status"
t.string "report_status_details"
t.string "report_viewer_url"
t.string "candidate_url"
t.bigint "provider_form_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "report_id"
t.string "candidate_middle_name"
t.json "sections_containing_alerts"
t.json "required_report_actions"
t.json "check_statuses"
t.string "adverse_action_status"
end
You do not have "Candidate" element in resp["ReportStatus"].
With your json:
resp["ReportStatus"] #=> {"Id"=>"7eac420d", "Status"=>"New", "StatusDetails"=>"New", "CheckStatuses"=>[]}
resp["Reportstatus"]["Candidate"] #=> nil
resp["ReportStatus"]["Candidate"]["FirstName"] #=> rise NoMethodError exception.
There is no "Candidate" key in parameters hash so it is failing. Use .dig method to access value from nested hash or return nil
From documentation:
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
UPD.
in line 7, 8, 9, 10 you have 'Candidate' key which could exist or could not exist in your hash.
replace resp["ReportStatus"]["Candidate"]["FirstName"] with resp.dig('ReportStatus', 'Candidate', 'FirstName') and you will have result if it exists or nil if 'ReportStatus', 'Candidate' or 'FirstName' doesn't exist. Then check whether your variable exist and
I have a angular/rails app in which users can search for a movie, and then add that movie to their watchlist.
This is the create function in angular,
createMovie.create({
release_date: releaseNL.release_date,
imdb_rating: $scope.movieImdbRating.imdbRating,
title: $scope.movieListID.original_title,
image: $scope.movieListID.poster_path,
movie_id: $scope.movieListID.id,
backdrop: $scope.movieListID.backdrop_path,
crew: $scope.movieCrew = response.credits.crew,
cast: $scope.movieCast = response.credits.cast
}).then(init);
And I have the cast and crew data in a scope,
$scope.movieCrew = response.credits.crew
$scope.movieCast = response.credits.cast
These scopes return multple objects, since there are multiple actors etc. involved.
I have a movies_controller.rb that has a create function like this,
def create
#movie = Movie.find_or_create_by movie_params
current_user.movies << #movie
redirect_to :root
current_user.followers.each { |follower| #movie.crete_activity(key: 'movie.create', owner: current_user, recipient: follower) }
end
private
def movie_params
params.require(:movie).permit(
:title,
:image,
:imdb_rating,
:release_date,
:movie_id,
:backdrop
:crew,
:cast
)
end
My movie model looks like this,
create_table "movies", force: :cascade do |t|
t.string "title"
t.string "release_date"
t.string "image"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "movie_id"
t.string "backdrop"
t.string "crew"
t.string "cast"
end
When I run the action the movie record gets created
{"id":26,"title":"The Neon Demon","release_date":"2016-01-21","image":"/gVB7zbrsk9TtLoVI4qob97q9v07.jpg","user_id":null,"created_at":"2016-01-07T12:31:49.956Z","updated_at":"2016-01-07T12:31:49.956Z","movie_id":"301365","backdrop":"/lSojrYBHLwiQQq6xr9Mec9f8wLM.jpg","crew":null,"cast":null}
But the crew and cast values are empty.
I've checked my rails console to see what happens when the movie is created,
Started POST "/movies.json" for 127.0.0.1 at 2016-01-07 13:31:49 +0100
Processing by MoviesController#create as JSON
Parameters: {
"release_date"=>"2016-01-21",
"imdb_rating"=>"N/A",
"title"=>"The Neon Demon",
"image"=>"/gVB7zbrsk9TtLoVI4qob97q9v07.jpg",
"movie_id"=>301365,
"backdrop"=>"/lSojrYBHLwiQQq6xr9Mec9f8wLM.jpg",
"crew"=>[
{"credit_id"=>"54881a95c3a368415c000cde", "department"=>"Directing", id"=>21183, job"=>"Director", name"=>"Nicolas Winding Refn", profile_path"=>"/tFqRY5LgXH6W9GV51xCiJS1y8mB.jpg"},
{"credit_id"=>"54881a9c925141520a000c5b", department"=>"Writing", id"=>21183, job"=>"Screenplay", name"=>"Nicolas Winding Refn", profile_path"=>"/tFqRY5LgXH6W9GV51xCiJS1y8mB.jpg"},
{"credit_id"=>"54881ac8c3a368414d000fd8", department"=>"Writing", ""=>1397141, job"=>"Screenplay", name"=>"Mary Laws", profile_path"=>nil},
{"credit_id"=>"54881ad792514151ff000e94", department"=>"Camera", id"=>63422, job"=>"Director of Photography", name"=>"Philippe Le Sourd", "profile_path"=>nil},
{"credit_id"=>"54881ae5c3a3684148000cc8", department"=>"Sound", id"=>8377, job"=>"Original Music Composer", name"=>"Cliff Martinez",
"profile_path"=>nil},
{"credit_id"=>"56424bf5c3a3686a5800064e", department"=>"Editing", id"=>966286, job"=>"Editor", name"=>"Matthew Newman", profile_path"=>nil}
],
"cast"=>[
{"cast_id"=>5, "character"=>"Jesse", "credit_id"=>"54cd541dc3a3687f8f002102", "id"=>18050, "name"=>"Elle Fanning", "order"=>1, "profile_path"=>"/ouQIwOEVvyDzsU5j0Iw4oRIuGW4.jpg"},
{"cast_id"=>6, "character"=>"Sarah", "credit_id"=>"54cd5425925141475700204d", "id"=>1036288, "name"=>"Abbey Lee", "order"=>2, "profile_path"=>"/tCLVClLA2dB43KdyqJusmcuvvEC.jpg"},
{"cast_id"=>7, "character"=>"", "credit_id"=>"54d3a598c3a3686abf003f08", "id"=>20089, "name"=>"Jena Malone", "order"=>3, "profile_path"=>"/tx5KR6dAhYag3plX7Rdg8t25QrC.jpg"},
{"cast_id"=>8, "character"=>"", "credit_id"=>"54d3a59c9251413fc1003e39", "id"=>6384, "name"=>"Keanu Reeves", "order"=>4, "profile_path"=>"/glCFGnKkX3QWxeLRYUMU1XTESHf.jpg"},
{"cast_id"=>9, "character"=>"", "credit_id"=>"54d3a5a59251413fc1003e3b", "id"=>110014, "name"=>"Christina Hendricks", "order"=>5, "profile_path"=>"/4pAtKssXy84DzJOgl5155KJNS5z.jpg"},
{"cast_id"=>10, "character"=>"", "credit_id"=>"54d3a5ad9251413fd6003e88", "id"=>234982, "name"=>"Bella Heathcote", "order"=>6, "profile_path"=>"/iqk9bZnrkLhIQbHPyrGcMK0Bzni.jpg"}
],
"movie"=>{
"title"=>"The Neon Demon",
"release_date"=>"2016-01-21",
"image"=>"/gVB7zbrsk9TtLoVI4qob97q9v07.jpg",
"movie_id"=>301365,
"backdrop"=>"/lSojrYBHLwiQQq6xr9Mec9f8wLM.jpg",
"crew"=>[
{"credit_id"=>"54881a95c3a368415c000cde", "department"=>"Directing", "id"=>21183, "job"=>"Director", "name"=>"Nicolas Winding Refn", "profile_path"=>"/tFqRY5LgXH6W9GV51xCiJS1y8mB.jpg"},
{"credit_id"=>"54881a9c925141520a000c5b", "department"=>"Writing", "id"=>21183, "job"=>"Screenplay", "name"=>"Nicolas Winding Refn", "profile_path"=>"/tFqRY5LgXH6W9GV51xCiJS1y8mB.jpg"},
{"credit_id"=>"54881ac8c3a368414d000fd8", "department"=>"Writing", "id"=>1397141, "job"=>"Screenplay", "name"=>"Mary Laws", "profile_path"=>nil}, {"credit_id"=>"54881ad792514151ff000e94", "department"=>"Camera", "id"=>63422, "job"=>"Director of Photography", "name"=>"Philippe Le Sourd", "profile_path"=>nil},
{"credit_id"=>"54881ae5c3a3684148000cc8", "department"=>"Sound", "id"=>8377, "job"=>"Original Music Composer", "name"=>"Cliff Martinez", "profile_path"=>nil},
{"credit_id"=>"56424bf5c3a3686a5800064e", "department"=>"Editing", "id"=>966286, "job"=>"Editor", "name"=>"Matthew Newman", "profile_path"=>nil}
],
"cast"=>[
{"cast_id"=>5, "character"=>"Jesse", "credit_id"=>"54cd541dc3a3687f8f002102", "id"=>18050, "name"=>"Elle Fanning", "order"=>1, "profile_path"=>"/ouQIwOEVvyDzsU5j0Iw4oRIuGW4.jpg"},
{"cast_id"=>6, "character"=>"Sarah", "credit_id"=>"54cd5425925141475700204d", "id"=>1036288, "name"=>"Abbey Lee", "order"=>2, "profile_path"=>"/tCLVClLA2dB43KdyqJusmcuvvEC.jpg"},
{"cast_id"=>7, "character"=>"", "credit_id"=>"54d3a598c3a3686abf003f08", "id"=>20089, "name"=>"Jena Malone", "order"=>3, "profile_path"=>"/tx5KR6dAhYag3plX7Rdg8t25QrC.jpg"},
{"cast_id"=>8, "character"=>"", "credit_id"=>"54d3a59c9251413fc1003e39", "id"=>6384, "name"=>"Keanu Reeves", "order"=>4, "profile_path"=>"/glCFGnKkX3QWxeLRYUMU1XTESHf.jpg"},
{"cast_id"=>9, "character"=>"", "credit_id"=>"54d3a5a59251413fc1003e3b", "id"=>110014, "name"=>"Christina Hendricks", "order"=>5, "profile_path"=>"/4pAtKssXy84DzJOgl5155KJNS5z.jpg"},
{"cast_id"=>10, "character"=>"", "credit_id"=>"54d3a5ad9251413fd6003e88", "id"=>234982, "name"=>"Bella Heathcote", "order"=>6, "profile_path"=>"/iqk9bZnrkLhIQbHPyrGcMK0Bzni.jpg"}
]
}
}
As you can see the parameters have the correct values, release_date, title, image, crew, cast etc. But it does not store the objects inside them. Also I'm unsure why there's a movie value in the parameters value that repeats all the data exept for imdbRating.
So my question is, how do I store the objects from crew and cast into the my movie record?
I want to check if a record already exist on database, but I have one json data type field and I need to compare it too.
When I try check using exists? I got the following error:
SELECT 1 AS one FROM "arrangements"
WHERE "arrangements"."deleted_at" IS NULL AND "arrangements"."account_id" = 1
AND "arrangements"."receiver_id" = 19 AND "config"."hardware" = '---
category: mobile
serial: ''00000013''
vehicle:
' AND "arrangements"."recorded" = 't' LIMIT 1
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "config"
LINE 1: ...id" = 1 AND "arrangements"."receiver_id" = 19 AND "config"."...
^
Code that I using to check if a exists:
#arrangement = Arrangement.new({account_id: receiver.account.id, receiver_id: receiver.id, config: params[:config], recorded: true})
if Arrangement.exists?(account_id: #arrangement.account_id, receiver_id: #arrangement.receiver_id, config: #arrangement.config, recorded: #arrangement.recorded)
puts 'true'
end
I already tried:
if Arrangement.exists?(#arrangement)
puts 'true'
end
But always return false
Table:
create_table :arrangements do |t|
t.references :account, index: true
t.references :receiver, index: true
t.json :config, null: false
t.boolean :recorded, default: false
t.datetime :deleted_at, index: true
t.integer :created_by
t.timestamps
end
You cannot compare jsons. Try to compare some jsons values
where("arrangements.config->>'category' = ?", params[:config][:category])
Look in postgresql docs for other JSON functions and operators
This will convert both field(in case it is just json) and the parameter(which will be a json string) to jsonb, and then perform a comparison of everything it contains.
def existing_config?(config)
Arrangement.where("config::jsonb = ?::jsonb", config.to_json).any?
end
I have a Star model model and included total_stars and average_stars columns to the Post model:
create_table "posts", force: true do |t|
t.string "title"
t.text "content"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.integer "average_stars", default: 0, null: false
t.integer "total_stars", default: 0, null: false
end
def calculate_total_stars
if [Post].include?(starable.class)
self.starable.update_column(:total_stars, starable.total_stars + self.number)
end end
def calculate_average_stars
if [Post].include?(starable.class)
self.starable.update_column(:average_stars, starable.total_stars / starable.stars.count)
end end
So now the problem is if the average_stars is 3.6 the end result is just 3. I'm not very sure what kind of calculating or approximation is suitable for a five star rating system. But I would like it to go in the following fashion: 1, 1.5, 2, 2.5...
Any suggestion of how to modify the average_stars column to achieve that result?
Instead of declaring your average column as an integer declare it as a float (or decimal):
t.float "average_stars", default: 0, null: false
Then when you're doing your calculation do:
def calculate_average_stars
if [Post].include?(starable.class)
self.starable.update_column(:average_stars, starable.total_stars.to_f / starable.stars.count)
end
end
Which will give you a decimal value instead of a rounded/truncated integer. The .to_f is the important part there.
If you want it to be rounded or only have a fixed number of decimal points either use a Decimal column in your migration (which takes a :limit) or do some mathy stuff:
((starable.total_stars.to_f / starable.stars.count) * 100).round / 100.0
def calculate_average_stars
if starable.is_a?(Post)
exact_average = starable.total_stars.to_f / starable.stars.count
rounded_average = exact_average - (exact_average % 0.5)
starable.update_column(:average_stars, rounded_average)
end
end