undefined method `first' for nil:NilClass on .each statement - ruby-on-rails

I need to join two very large tables from a single database in Rails. To reduce the amount of database calls, I want to use the .includes method. However, when I use this on my query I get the following error:
undefined method `first' for nil:NilClass on <% table_data.each do |d| %>
I can't for the life of my understand where this is coming from and how to fix it.
My code:
class Planning < ApplicationRecord
has_many :timeline, :foreign_key => "pcnrtoev"
end
class Timeline < ApplicationRecord
belongs_to :Planning
end
class OverviewController < ApplicationController
def index
#table_data = Planning.select(:pcnrtoev).includes(:timeline)
end
end
index.html.erb
<%= render partial: 'datatable', locals: {table_data: #table_data} %>
_datatable.html.erb
***
<% table_data.each do |d| %>
-do things-
<% end %>
****
Edit
If I change my initial query to
#table_data = Planning.select(:pcnrtoev).includes(:timeline).limit(50)
it seems to work. I still can't get to the data however, as it throws up this:
undefined method `telco_code_in' for #<Timeline::ActiveRecord_Associations_CollectionProxy:0x00007f4880c13638>
at
<% table_data.each do |d| %>
<%=if d.timeline.any?
>> telco_code_in = d.timeline.telco_code_in
end %>
<%end%>
Edit
It seems there is something wrong with the information I get from the database, rather than from the query itself. If I limit the output to 5000, everything is fine. If I limit the output to 10.000, I get the "No first method on .each"-error. Could this be because the Planning table can have one or more rows in the Timeline table?
Planning schema
create_table "planning", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "pcnrtoev", null: false
t.string "postcode", null: false
t.string "huisnr", null: false
t.string "toev"
t.index ["huisnr"], name: "huisnr"
t.index ["id"], name: "id"
t.index ["pcnrtoev"], name: "pcnrtoev"
t.index ["pcnrtoev"], name: "pcnrtoev_unique", unique: true
t.index ["postcode"], name: "postcode"
t.index ["toev"], name: "toev"
end
The Timeline table is a view instead of a table, so I don't have an actual schema for that. I scraped the code of everything that could cause the error, so this is my complete code for now.
Also, this is a pre-existing database, maybe that's useful information?

It is hard to give you right code without schema but I will do my best to explaining your mistake. So when you write
Planning.select(:pcnrtoev)
you are basically saying to database. Hey give me all pcnrtoev columns from planing table. If you paste this code in console you will se something like this
<Planning id: nil, pcnrtoev: INT>,
<Planning id: nil, pcnrtoev: INT2>...
So your database returns you only pcnrtoev columns and all other columns are nill.(Including columns from timeline table).
I hope you see what went wrong.
If you still have problem with writing the query, give me schema of Planing and Timeline table.

Related

Can you have an association based on a JSONB column in Rails?

I have a Rails app (rails v6.0.3, ruby 2.7.1) that is using the Noticed gem to send notifications. I have the following model configuration:
class Vendor < ApplicationRecord
has_noticed_notifications
end
The has_noticed_notifications is, as described in their README, a "Helper for associating and destroying Notification records where(params: {param_name.to_sym => self})"
So when I create a Notification like so...
VendorAddedNotification.with(
vendor: vendor,
data_source: "user",
).deliver(some_user) # => Notification inserted!
I expect to be able to find the Notifications that reference the vendor, using the Noticed method, like so:
vendor = Vendor.find ...
vendor.notifications_as_vendor # => Expected: [ Notification#123 ]
However, the input is always an empty array (Actual => [])
I looked at their source code and it looks like notifications_as_vendor is the following query:
Notification.where(params: { :vendor => self }) # where self = an instance of the Vendor model
However, that doesn't seem to work, and I'm not sure if it's supposed to or not. I tried running a simpler query to see if it worked ...
Notification.where(params: { :data_source => "user" })
But that did not work either. However, when I ran the same query with a different signature, it did:
Notification.where("params->>'data_source' = ?", "user")
So my question is-- is this Notified's mistake, or am I missing something in my configuration? I'm using PSQL for this, here is the relevant schema:
...
create_table "notifications", force: :cascade do |t|
t.string "recipient_type", null: false
t.bigint "recipient_id", null: false
t.string "type", null: false
t.jsonb "params"
t.datetime "read_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["read_at"], name: "index_notifications_on_read_at"
t.index ["recipient_type", "recipient_id"], name: "index_notifications_on_recipient_type_and_recipient_id"
end
...
And here are the related models:
class VendorAddedNotification < Noticed::Base
deliver_by :database
param :vendor
param :data_source
end
class Notification < ApplicationRecord
include Noticed::Model
belongs_to :recipient, polymorphic: true
end
Thank you in advance!
I've found why it's not working, it seems to be an issue with Notified.
In plain SQL I ran:
# PLAIN SQL
select "params" from "notifications" limit 1
Which returns the notification's params (returned notifcation's id=77)
# PLAIN SQL Result
"{""added_by"": {""_aj_globalid"": ""gid://stack-shine/WorkspaceMember/269""}, ""data_source"": ""user"", ""_aj_symbol_keys"": [""workspace_vendor"", ""data_source"", ""added_by""], ""workspace_vendor"": {""_aj_globalid"": ""gid://stack-shine/WorkspaceVendor/296""}}"
Now in Rails when I do
vendor = Notification.find(77).params[:vendor]
vendor.notifications_as_vendor.to_sql
The result is ...
"SELECT \"notifications\".* FROM \"notifications\" WHERE \"notifications\".\"params\" = '{\"vendor\":{\"_aj_globalid\":\"gid://stack-shine/Vendor/296\"},\"_aj_symbol_keys\":[\"vendor\"]}'"
... the extracted params from that query are:
'{\"vendor\":{\"_aj_globalid\":\"gid://stack-shine/Vendor/296\"},\"_aj_symbol_keys\":[\"vendor\"]}'
So ... In the database, the serialized params are A, but Rails is search for B:
# A: `params` In the database
"{""added_by"": {""_aj_globalid"": ""gid://stack-shine/WorkspaceMember/269""}, ""data_source"": ""user"", ""_aj_symbol_keys"": [""vendor"", ""data_source"", ""added_by""], ""vendor"": {""_aj_globalid"": ""gid://stack-shine/Vendor/296""}}"
# B: `params` Searched with by Rails
"{\"vendor\":{\"_aj_globalid\":\"gid://stack-shine/Vendor/296\"},\"_aj_symbol_keys\":[\"vendor\"]}"
Clearly this query could not work because the params in the database are not the params being search by Rails.
The notification, in the database, has extra parameters on top of "vendor" ("data_source" and "added_by") that are not being search up by the Vendor. Is this why it returns nothing?
For now, I'll simply the look up the notifications myself by storing the vendor_id in params and doing something like Notification.where("params >> vendor_id = ?", 123)

Trying to save JSON data to database

I'm not sure what I'm missing, but the following code below isn't saving to my database. I'm using rails 5.1.4 and ruby 2.4.1. I have no controller or views and using Mysql if that's of any help.
Model
class Agent < ApplicationRecord
json = JSON.parse('{"Agents":[{"firstName":"John","lastName":"Smith","id":"57fa5f47-8851-11e7-b391-02cbcf8dd991"},{"firstName":"Alice","lastName":"Thompson","id":"77eccb07-101d-11e7-83be-02e5025d7d75"}]}')
json['Agents'].each do |data|
Agent.create(
id: data['id'],
first_name: data['firstName'],
last_name: data['lastName']
)
end
end
Schema
create_table "agents", id: :string, limit: 36, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.string "first_name"
t.string "last_name"
end
As Gokul M pointed out, firstName and lastName need to be snake-cased to match your schema. However, your code still will not work, because :id is a protected attribute in ActiveRecord models. Meaning that it can’t be mass-assigned. You can however assign it normally. i.e model.id=foo
So you could try something like
json['Agents'].each do |data|
agent = Agent.new
agent.id = data['id']
agent.first_name = data['firstName']
agent.last_name = data['lastName']
agent.save
end
Though, I haven’t tested this code.

Rails 4: Change an integer from the index view

I am making a clone of Joe's Goals. I have a very rough version of this that I built with a rails scaffold. The week's table has 7 days and the name of a habit. My problem is how do I increase the number of occurrences (integer representing how often you did that habit) from the index.html.erb view, without entering the edit form.
I looked at a couple of things on S.O. building custom methods in my controller.
How can I make a link_to in my index table field so that it changes the integer.
Here is what my Table looks like. The way its set up is like this:
class CreateWeeks < ActiveRecord::Migration
def change
create_table :weeks do |t|
t.string :habit
t.boolean :gob
t.integer :sunday
t.integer :monday
t.integer :tuesday
t.integer :wednesday
t.integer :thursday
t.integer :friday
t.integer :saturday
t.timestamps null: false
end
end
end
As you can see i've put a couple up/down arrows to show what I am trying to do. Right now they just link to my root path. As I said, I would like them to change the value of the integer in the selected field. I've only put the arrows on monday in this image btw.
in my controller I have:
def increase
#week = Week.find(params[:id])
#week.update_attribute("monday", monday += 1)
#week.save
flash[:notice] = "Habit Increased."
end
I am trying to increase the integer with this link:
<%= link_to "+ 1", :method => :increase, :monday => #increase %>
Are you using AJAX? If you are not, then you should be using AJAX on rails. You can check this documentation and also may be this video will help you in achieving what you need.

How to select objects based on their enum value and then sum another field belonging to the object collection in Rails

So, I'm using Rails 4, and I have an enum column on my "Sales_Opportunity" object called pipeline_status - this enables me to move it through a sales pipeline (e.g. New Lead, Qualified Lead, Closed deal etc). This all works fine. I'm able to find the number of sales_opportunities that a company has by status through using the following:
<%= #company.sales_opportunities.where(pipeline_status: 3).count %>
This all works fine. What I want to do is to find all sales_opportunities that have the pipeline_status of "closed_won" (enum value of 4 in my app) and sum the value of each won deal (so I can represent the total value of the customer based on the deals that are won in the system). A Sales_Opportunity in my model has a sale_value field, so I tried:
<%= #company.sales_opportunities.where(pipeline_status: 4).each.sale_value.sum %>
which returns the following error:
undefined method `sale_value' for #<Enumerator:0x007f9b87a9d128>
This is probably a trivial error but I can't for the life of me figure out what's going on. Is there where statement returning the enumerator or the sales_opportunity objects with that enumerator? Any help would be gratefully appreciated.
If it helps here are the fields in my sales_opportunities table:
create_table "sales_opportunities", force: true do |t|
t.datetime "close_date"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "pipeline_status", default: 0
t.string "opportunity_name"
t.integer "company_id"
t.decimal "sale_value", precision: 15, scale: 2, default: 0.0
end
A Sales_opportunity belongs_to a Company Object and a User Object, if that makes any difference.
use aggregate function sum
<%= #company.sales_opportunities.where(pipeline_status: 4).sum(:sale_value) %>
Other possibility is to use
<%= #company.sales_opportunities.where(pipeline_status: 4).pluck(:sale_value).reduce(0, :+) %>

Sort by date span

Let's say we have the following model.
create_table :meetings do |t|
t.datetime :started_at
t.datetime: ended_at
end
class Meeting < ActiveRecord::base
end
How would I order a meetings_result, so that the longest meeting is the first meeting in the collection and the shortest meeting the last.
Something like
Meeting.order(longest(started_at..ended_at))
Obviously that doesn't work.
How would I achieve this, preferably without using raw SQL?
I don't think you can do it without using raw SQL.
Using Raw SQL:
Meeting.order('(ended_at - start_at) DESC')
(works with PostGreSQL)
No SQL? Two options come to mind. Create an array of hashes and sort it there, or add another column in the db and sort on that.
# How many records in the meetings table? This array of hashes could get huge.
meetings_array = []
Meeting.all.each do |meeting|
meetings_array << {id: meeting.id, started_at: meeting.started_at, ended_at: meeting.ended_at , duration: meeting.ended_at - meeting.started_at }
end
meetings_array.sort_by { |hsh| hsh[:duration] }
Or, create another column:
# Is it worth adding another column?
create_table :meetings do |t|
t.datetime :started_at
t.datetime :ended_at
t.datetime :duration
end
Update this column whenever you have both started_at and ended_at. Then you can:
Meeting.order("duration")

Resources