Rails: Query nil has_one association - sql

I have two models: Patient and CodeStatus.
CodeStatus belongs_to Patient, and Patient has_one CodeStatus
I am trying to query all patients where patient.code_status is nil. I was surprised to find that Patient.where(code_status: nil) does not work throwing: column patients.patient_id does not exist
I have already found this (fairly old) answer, but I find it difficult to believe that the best way to query this is via a long string of raw SQL. I would think that rails would include this helper like they do for many other associations. Does anyone know of a less verbose solution to this? Thanks in advance.

The problem is, that
patient.code_status
is not a column, but a method, added by Rails when you say
class Patient
has_one :code_status
end
Here is how you'd get all patients not associated with any code status:
Patient.includes(:code_status).where(code_statuses: { id: nil })

Related

Rails ActiveRecord Access association's(children?) latest created objects

as title said i am trying to access an array of objects of an association
This is a has_many association
here is my class
class Keyword < ApplicationRecord
has_many :rankings
end
class Ranking < ApplicationRercord
belongs_to :keyword
end
There are a attribute in ranking called position:integer, i want to be able to access all latest created rankings from all keyword here is what i got so far
Keyword.all.joins(:rankings).select( 'MAX(rankings.id) ').pluck(:created_at, :keyword_id, :position)
i've read some other post suggesting me to use MAX on rankings.id, but i am still not able to return the array
At the moment Keyword.count return 4597
Ranking.count return 9245
Each keyword has generated about 2 rankings, but i just want the latest ranking from each keyword in array format, so to get latest of each i should expect around 4597
Not sure if i explained clear enough, hope u guys can help me :'( thanks really appreciate it
If you are using Postgres. You can use DISTINCT ON
Keyword.joins(:rankings)
.select("DISTINCT ON(ratings.keyword_id) keywords.*, ratings.position, ratings.created_at AS rating_created_at")
.order("ratings.keyword_id, ratings.id DESC")
Now you can access position, rating_created_at
#keywords.each do |k|
k.position
....
#keywords.map { |k| [k.id, k.rating_created_at, k.position] }
If you have enough rankings you might want to store the latest ranking on the on keywords table as a read optimization:
class Keyword < ApplicationRecord
belongs_to :latest_ranking, class_name: :ranking
has_many :rankings, after_add: :set_latest_ranking
def set_latest_ranking(ranking)
self.update!(latest_ranking: ranking)
end
end
Keyword.joins(:latest_ranking)
.pluck(:created_at, :id, "rankings.position")
This makes it both very easy to join and highly performant. I learned this after dealing with an application that had a huge row count and trying every possible solution like lateral joins to improve the pretty dismal performance of the query.
The cost is an extra write query when creating the record.
Keyword.joins(:rankings).group("keywords.id").pluck("keywords.id", "MAX(rankings.id)")
This will give you an array which elements will include an ID of a keyword and an ID of the latest ranking, associated with that keyword.
If you need to fetch more information about rankings rather than id, you can do it like this:
last_rankings_ids_scope = Ranking.joins(:keyword).group("keywords.id").select("MAX(rankings.id)")
Ranking.where(id: last_rankings_ids_scope).pluck(:created_at, :keyword_id, :position)

Rails eager loading and conditions

I have the following associations set up
class bookinghdr
belongs_to :agent
end
class bookingitem
belongs_to :bookinghdr, :include => agent
end
So I was expecting to be able to do the following:
named_scope :prepay, :include=>["bookinghdr"], :conditions => ["bookinghdr.agent.agenttype = 'PP'"]
and in my controller do:
b = Bookingitem.prepay
But that gives me a ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'bookinghdr.agent.agenttype'
However if I don't include the conditions clause then I get a recordset on which I can do:
b = Bookingitem.prepay
b[0].bookinghdr.agent.agenttype
without any error!
I don't want to have to get all the records and then iterate over them to find the ones whose agent has a 'PP# flag. I was hoping that AR would do that for me.
Anybody got any ideas on how to achieve this?
Your question shows that you have not yet fully understood how associations and named scopes work. Since I cannot tell from your question what parts aren't clear, I suggest you read the Association Basics guide at http://guides.rubyonrails.org/v2.3.11/association_basics.html. This should bring you up to speed regarding the concepts you want to implement. After you have read the guide it should all make sense.

find_by_or_initialize_by*_and_* and increment!

I'm a little slow when it comes to programming, but I enjoy it and try to learn as much as I can. So apologies in advance if i'm not clear enough with my question.
I have an app with a sole purpose for DJs to review songs and the song reviews to be grouped by state. 5 Fields… songid, state, stdjlike, stthinkclublike, stplait (Horrible names I know, but they help me to remember what they are :)).
Once a user decides to review a song, they will be asked 3 questions which are answered on a scale of 1 to 5, the form collects the 3 answers as well as the users state and the id of the song. Most of the time the database will just find the state and song id, then add the new data to the 3 question fields (djlike, thinkclublike, plait). But the first review for a song from a user in a new state will have to create a new record.
So again, just to be crystal clear, the first time a user from "New York" reviews (song) "123" it will create a new record with the song id, state name, and add the review values to the 3 columns stdjlike, stthinkclublike and stplait. Next user, a different user, from NY for song "123" creates a review, it will just update the record, adding the values for stdjlike, stthinkclublike and stplait to the previous review.
I have been looking around all week and finding lots of good solutions to similar but not the same problems (A well documented solution here). I decided to go with increment because I'm updating with values that aren't fixed numbers, I've been trying but I just can't get it to work.
I started with what works. The create in the reviewstate controller saves the record when it is just a simple .new and .save. So i changed it from there to this..
def create
#reviewstate = Reviewstate.find_or_initialize_by_songid_and_state(song.id, current_user.state)
#reviewstate.stdjlike.increment!(:stdjlike, reviewstate.stdjlike)
#reviewstate.stthinkclublike.increment!(:stthinkclublike, reviewstate.stthinkclublike)
#reviewstate.stplait.increment!(:stplait, reviewstate.stplait)
#reviewstate.save!
end
Stops working. I have tried different variations and methods, this is when I feel the closest. Please help me, its been 4 days :).
Thank you in advance.
If find does not return a record and a new one is initialized, stdjlike will be nil (unless you set another default value in your migration). Thus, increment will try to increment by Nil. Nil is not a fixnum though and it will not work. I suppose that this is the error you get.
Moreover, you store your object in #reviewstate and then you refer to it by reviewstate.stdjlike. You have an instance variable #reviewstate and you then refer to the local variable reviewstate. They are two different objects.
You should handle things differently. Check if your record exists. If yes, just get it. If not, create it. Use Reviewstate.exists?.
Also, bear in mind that Ruby uses underscores as a convention for names. That would make it something like std_jlike or something like that. But you can always use what you feel more comfortable with.
This is going to sound pretty drastic, but I think will make things simpler for you in the long run. I'm going to recommend a pretty big refactor. It sounds like you have a few things which should be their own models:
User (or DJ)
State
Song
Review
I would structure the application so that your reviews belong to one state, one user, and one song:
#user.rb
class User << ActiveRecord::Base
has_many :reviews
end
#state.rb
class State << ActiveRecord::Base
has_many :reviews
end
#song.rb
class Song << ActiveRecord::Base
has_many :reviews
end
#review.rb
class Review << ActiveRecord::Base
belongs_to :user
belongs_to :state
belongs_to :song
end
Then when you want to gather some form of statistics for display you derive it from the review records. This will allow you to get stats across states, DJ's, or songs.
This is also a more RESTful implementation where more objects in your system are represented as resources that can be (C)reated, (R)ead, (U)pdated, and (D)estroyed. In general doing things in a RESTful manner will make your life much easier when working in rails.

Why is a SystemStackError caused with a has_many association in Ruby on Rails?

I stumbled over a SystemStackError and found where it is caused in the source code. Though, I did not quite understand why it happens. Maybe you can help me.
Here is the scenario:
There are two models Facility and Location given by their model definitions in the following.
class Location < ActiveRecord::Base
belongs_to :facility
accepts_nested_attributes_for :facility
end
class Facility < ActiveRecord::Base
has_many :locations
accepts_nested_attributes_for :locations
end
Now I create an object of each class in the Rails console: rails c.
location = Location.create(...)
facility = Facility.create(...)
Then I want to associate both with each other.
location.facility = facility
facility.locations << location
I cannot execute the last command when I executed the first before - it raises a SystemStackError: stack level too deep. Though, I can run the association commands separate from each other or sequential but in reverse order. The problem is that I cannot add the location again. Why?
Why do both?
This line:
facility.locations << location
Will already set the location's facility to be the specified facility. Both lines in this case are doing the same thing. What I would recommend doing is to use the association builder, like this:
facility.locations.create!(...)
This way, Rails takes care of setting the facility_id field, rather than you doing a manual assignment after it.
The first thing that I would suspect here is that the has_many association is really has_too_many. In other words, you may have too many locations in the relationship.
In fact, given the code you posted, you seem to have created an infinite loop of associations. You wrote:
accepts_nested_attributes_for :facility
I am assuming that this causes ActiveRecord to open the facility attribute where it finds another location with yet another facility attribute ad infinitem. before you dig too deeply, try this to see if it works:
facility.locations << location
location.facility = facility
However, be wary because this might just push the stack error to some other place in the app. If you Google for that error message you can find several people who have run into infinite recursion problems, generally related to saving a record.

Help with Rails find_by queries

Say if #news_writers is an array of records. I then want to use #news_writers to find all news items that are written by all the news writers contained in #news_writers.
So I want something like this (but this is syntactically incorrect):
#news = News.find_all_by_role_id(#news_writers.id)
Note that
class Role < ActiveRecord::Base
has_many :news
end
and
class News < ActiveRecord::Base
belongs_to :role
end
Like ennen, I'm unsure what relationships your models are supposed to have. But in general, you can find all models with a column value from a given set like this:
News.all(:conditions => {:role_id => #news_writers.map(&:id)})
This will create a SQL query with a where condition like:
WHERE role_id IN (1, 10, 13, ...)
where the integers are the ids of the #news_writers.
I'm not sure if I understand you - #news_writers is a collection of Role models? If that assumption is correct, your association appears to be backwards - if these represent authors of news items, shouldn't News belong_to Role (being the author)?
At any rate, I would assume the most direct approach would be to use an iterator over #news_writers, calling on the association for each news_writer (like news_writer.news) in turn and pushing it into a separate variable.
Edit: Daniel Lucraft's suggestion is a much more elegant solution than the above.