Store multiple objects into one record - ruby-on-rails

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?

Related

Receiving NoMethodError (undefined method `[]' for nil:NilClass): from webhook callback Rails 6

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

Rails how to order column sum query?

I want to ranking presentation.
Recommend model has user and count.
rank / user_name / recommend_count
1 / A / 4
2 / B / 2
3 / C / 1
code
#top_recommenders = Array.new
recommends = Recommend.group(:user_id).order("sum_count DESC").limit(10).sum(:count)
recommends.each do |recommend|
recommend = Recommend.where(user_id: recommend[0]).first
recommend.count = recommend[1]
#top_recommenders << recommend
end
schema
create_table "recommends", force: :cascade do |t|
t.integer "user_id"
t.integer "recommender_id"
t.integer "count"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["recommender_id"], name: "index_recommends_on_recommender_id"
end
That worked, but is not perfect.
How to query group by 'user' and order 'sum_count'?
#top_recommenders = Recommend.select('recommends.*,SUM(count) as count_total').group(:user_id).order("count_total DESC").limit(10)
The sum of count will be stored in the query result as count_total.
If you need to store it as each recommend objects count, then map it as below.
#top_recommenders.map { |r| r.count = r.count_total }

How to make a request to the grouping of results by date?

I can not write a valid request to obtain such a result (group recording day):
date (days) - sum (field1) - sum (field2)
date (days) - sum (field1) - sum (field2)
etc.
My code:
document has_many downloads
download belongs_to document
#downloads = Download
.order(created_at: :desc)
.includes(:document)
.joins(:document)
.where('documents.uploaded_by = ?', params[:user_id])
.page(params[:page])
I would be grateful for the help
UPD:
create_table "document_downloads", force: :cascade do |t|
t.integer "document_id", null: false
t.integer "payment_sum"
t.datetime "created_at", null: false
end
i want to group like:
payment_sum created_at
150 2015-06-27
120 2015-06-27
I need more information about your table schema but this can be helpful..
Download.select('date(days), sum(field1), sum(field2)')
.joins(:document)
.where('documents.uploaded_by = ?', params[:user_id])
.group('days')
.page(params[:page])

How to compare if a record exist with json data type field?

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

How to force a column to only allow number in this fashion: 1, 1.5, 2, 2.5 and so on?

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

Resources