Using select on ActiveRecord throws "no route matches" - ruby-on-rails

Something really weird is happening and I am not sure how to fix it.
I have two classes on my DB. Projects and timelogs.
On my index method for the Projects controller, I am listing a list of projects and I also want to list the last date where any log was entered for that project.
When I run the code below, I get the following error.
No route matches {:action=>"show", :controller=>"projects", :id=>#<Project id: nil, name: "Project A", user_id: 1, created_at: nil, updated_at: nil>}
If I comment the "select" line and uncomment the line above (with Project.all), everything works perfectly.
Any idea why this could be happening?
def index
##projects = Project.all
#projects = Project.select("julianday(Date('now')) - julianday(log.timeofevent) as diff, *").joins("left outer join timelogs log on projects.id = log.project_id").group("projects.id").order("log.timeofevent asc")
respond_to do |format|
format.html # index.html.erb
format.json { render json: #projects }
end
end

Most likely this is being caused because in the view your code is attempting to create links that will show each project. This might look like this:
link_to 'Show', project
Or maybe like this:
link_to 'Show', project_path(project)
Something in your query is returning rows with no projects.id (as you can see in the error, the id is nil). I suspect your error is the group function, that needs an aggregate function so it definitely seems wrong, I'm not sure immediately why it would be causing a null project.id. Did you mean to use order?
Added
I just realized that you're probably using it to get rid of duplicates. So anyway, back to where everyone else was then I think - more info needed :)

thanks a lot for the answers. You were right on. Indeed, the issue was when the view tried to generate a link and it was missing things like the ID.
I thought that this would work:
#projects = Project.select("julianday(Date('now')) - julianday(log.timeofevent) as diff, *").joins("left outer join timelogs log on projects.id = log.project_id").group("projects.id").order("log.timeofevent asc")
But, apparently "(star)" alone won't work. I had to replace it to "projects.(star)"
So, the resulting fixed code (works fine) is:
#projects = Project.select("julianday(Date('now')) - julianday(log.timeofevent) as diff, projects.*").joins("left outer join timelogs log on projects.id = log.project_id").group("projects.id").order("log.timeofevent asc")

Related

Rails: update existing has_many through record via controller?

So two thirds of this works. Every time a User reads an Article, a History record is created (has_many through), which just says "User read Article at Read_Date_X".
The database is ok, the models are ok, the read_date param is permitted in the History controller, and the following operation works both 1) to check if a User has read an article before and 2) to create a new History record if it is the first time on this article.
But I cannot work out why the middle bit (to just update the read_date on an existing record) is not working. It doesn't matter if I try it with h.save! or h.update().
h = History.where(article_id: #article, user_id: current_user)
if h.exists?
h = History.where(article_id: #article, user_id: current_user)
h.read_date = Time.now
h.save!
else
h = History.new
h.article_id = #article.id
h.user_id = current_user.id
h.read_date = Time.now
h.save!
end
The error it throws if it finds an existing record is:
undefined method `read_date=' for #<History::ActiveRecord_Relation:0x007fe7f30a5e50>
UPDATE: working answer
So Derek was right, and this version works. The middle bit needed a single instance, not an array, which is what the top conditional (without .first) was checking for. Using that to return a single record, though, means you need to swap "exists?" to "present?" in the second part.
h = History.where(article_id: #article, user_id: current_user).first
if h.present?
h.read_date = Time.now
h.save!
else
h = History.new
h.article_id = #article.id
h.user_id = current_user.id
h.read_date = Time.now
h.save!
end
History.where(article_id: #article, user_id: current_user) is returning a History::ActiveRecord_Relation. If you want to set the read_date, you'll want to get a single record.
Here's one way you could do this with what you have currently:
h = History.where(article_id: #article, user_id: current_user).first
Another way you could handle this is by using find_by instead of where. This would return a single record. Like this:
h = History.find_by(article_id: #article, user_id: current_user)
However, if it's possible for a user to have many history records for an article, I would stick to the way you're doing things and make one change. If for some reason you have a lot of history records, this may not be very efficient though.
histories = History.where(article_id: #article, user_id: current_user)
histories.each { |history| history.update(read_date: Time.now) }
I realize this question is already answered. Here are a couple of additional thoughts and suggestions.
I would not have a separate read_date attribute. Just use updated_at instead. It's already there for you. And, the way your code works, read_date and updated_at will always be (essentially) the same.
When looking up whether the history exists, you can do current_user.histories.where(article: #article). IMO, that seems cleaner than: History.where(article_id: #article, user_id: current_user).first.
You can avoid all that exists? and present? business by just checking if the h assignment was successful. Thus, if h = current_user.histories.where(article: #article).
If you go the route of using updated_at instead of read_date, then you can set updated_at to Time.now by simply doing h.touch.
I would use the << method provided by has_many :through (instead of building the history record by hand). Again, if you use updated_at instead of read_date, then you can use this approach.
So, you could boil your code down to:
if h = current_user.histories.where(article: #article)
h.touch
else
current_user.articles << #article
end
You could use a ternary operator instead of that if then else, in which case it might look something like:
current_user.histories.where(article: #article).tap do |h|
h ? h.touch : current_user.articles << #article
end

Add information to DB from the controller on rails

I'm trying to add the user id to 2 columns in the db after a user successfully submits a form. The commands run fine in the console but wont work in the controller. I haven't added info to the db straight from the controller before so I'm thinking that I'm doing something wrong.
Here is the create method
def create
#game = Game.friendly.find(params[:game_id])
#game_category = Game.friendly.find(#game.id).game_categories.new(game_category_params)
if current_user.mod_of_game? params[:game_id] && #game_category.save
Game.find(#game.id).game_categories.find(game_category.id).update(submitted_by: current_user.id)
Game.find(#game.id).game_categories.find(game_category.id).update(approved_by: current_user.id)
flash[:info] = "Game category added succesfully!"
redirect_to #game
else
render 'new'
end
end
These 2 lines are supposed to add the user id to the submitted_by and approved_by columns but they don't. I don't get any error messages, they simply just don't add anything to those columns
Game.find(#game.id).game_categories.find(game_category.id).update(submitted_by: current_user.id)
Game.find(#game.id).game_categories.find(game_category.id).update(approved_by: current_user.id)
If I replace the lines with coding that works in the console to see if its a variable or something thats not right it still doesn't work
Game.find(12).game_categories.find(55).update(submitted_by: 1)
Game.find(12).game_categories.find(55).update(approved_by: 1)
I'm building an app to learn rails and I guess this is something I just don't know.
Can anyone enlighten me on what I'm doing wrong?
Update:
Ok it is now giving me an error - Couldn't find GameCategory without an ID
So the #game_category.id isn't working?
It is a small typo in your query you missed #.
Game.find(#game.id).game_categories.find(#game_category.id).update(submitted_by: current_user.id)
After playing around tweaking it here and there it turns out to be this line
if current_user.mod_of_game? params[:game_id] && #game_category.save
when changed to this it works
if #game_category.save && current_user.mod_of_game? #game

Couldn't find Post with 'id'=params[:id]

If I don’t have row with id=params[:id] how can i check it, since
when I write
def show
#post=Post.find(params[:id])
if #post.nil?
#post={
title:"No such post",
post:"No such post"
}
end
end
I get error.
From the fine manual:
find(*args)
Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). If no record can be found for all of the listed ids, then RecordNotFound will be raised.
So if find can't find anything with the id you're looking for, it raises an ActiveRecord::RecordNotFound exception rather than return nil like you want it to. That exception ends up being handled deep inside Rails and gets converted to a 404.
You could trap that exception yourself:
def show
#post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
#post = {
title: "No such post",
post: "No such post"
}
end
Note that you'd only trap the specific exception you're expecting to see, a bare rescue is almost always a mistake because it can hide bugs.
You could also use find_by:
find_by(*args)
[...]
If no record is found, returns nil.
like this:
def show
#post = Post.find_by(:id => params[:id])
if #post.nil?
#post = {
title: "No such post",
post: "No such post"
}
end
end
Exceptions are meant for handling errors and other exceptional conditions, they're not meant to be used for normal flow control. I'd probably use find_by for this sort of thing; it seems that you're expecting the occasional missing record so it a missing record isn't really an error or an unexpected condition.
show controller is expected to show existing elements only. When an element (Post instance) does not exist, find throws an exception. As #Michal suggested in the comments, usually non-existing entities are being handled with 404 response, or like.
For the time, being, though, you might cheat Rails with:
#post = Post.find(params[:id]) rescue {
title: "No such post",
post: "No such post"
}
This is not a production solution, of course, but it might help during learning phase.

Add Conditions in the Controller to filter through records to pass the correct instance to the view

This should be a small question. I have a students table and a classifieds table in my schema. The model/table relationship is hook up in a way that when I do
#student = Student.first.classifieds.all
in the rails console I will get all the classifieds ad for this particular student
[#<Classified id: 3, ad_content: "在BC附近BU也可以、需要女生一起租房子、看了几处、俩人去租非常合算、限女生", ad_title: "BU和BC旁边的房子求室友一起租 ", student_id: 16, created_at: "2013-09-17 19:20:43", updated_at: "2013-09-17 19:49:31", location: "Allston">, #<Classified id: 1, ad_content: "Malden Towers 宽敞客厅出租,附带阳台,窗外是公寓的花园,客厅可用窗帘或木板隔开, 每月4...", ad_title: "Malden Towers 客厅出租 400/月", student_id: 16, created_at: nil, updated_at: "2013-09-17 19:47:55", location: "Malden">]
I am trying to filter through the records with specific conditions so only the records that satisfy this specific condition can be passed to the view therefore appear on that particular page.
I want to display the record only if the location is equal to malden.
in my students_controller.rb I have this
def malden_index
#student = Student.first
for classified in #student.classifieds.all
return classified if classified['location'] == 'Malden'
end
I have this in my view
<%= classified.ad_content %>
I am getting this error
undefined local variable or method `classified'
I have three questions
can I add the conditions in my view ? or does it have to be in my controller?
are my records returned to me in array data type?
What is the problem in my code? (I think its pretty straight forward)(the classified should be each record, then return the record only if the location key is equal to malden)
You shouldn't add this condition filtering in the view. It's much better when done in the controller.
The filtering can be done in multiple ways. It is usually best and fastest to let the database do the work:
#student.classifieds.where(:location => 'Malden').all
You can either forward variables by making them an instance variable #classifieds or pass it as local variable to your view with render 'malden_index', :locals => {:classifieds => #student.classifieds.all}
In general, the approach with return in your for-loop doesn't result in your desired filter. Either use my suggestion from #2 or build your array like this
#classifieds = []
for classified in #student.classifieds.all
#classifieds << classified if classified['location'] == 'Malden'
end
Or shorter and more 'ruby-way':
#classifieds = #student.classifieds.keep_if{|cf| cf['location'] == 'Malden'}
You could then access the #classifieds array in your view. Still, I very much suggest you rather use a database filter if possible.
First off: in the view you can only reach instance variables defined in the controller. So the for loop does not gather anything that is reachable in the view.
So you could fix that by doing
def malden_index
#student = Student.first
#classifieds = #student.classifieds.where('location="Malden"')
end
and in your view iterate over all the #classifieds.
Now notice: this is completely hardcoded.
I would solve this as follows: instead of using a separate index method, use the show action (of a student), check if a location is given, and if so, filter the classifieds accordingly.
That would look like this
def show
#student = Student.find(params[:id])
#classifieds = #student.classifieds
if params[:location]
#classifieds = #classifieds.where('location = ?', params[:location]
end
end
and then you would build the url as follows /students/1?location=malden.
If you then add the following route to config/routes.rb
get '/students/:id/:location', to: 'students#show'
you could improve that to /students/1/malden.
def malden_index
#student = Student.first
#classified = #student.classifieds.find_by_location("Malden")
end
In view:
<%= #classified.ad_content %>
Try this...
def malden_index
#student = Student.first
#classified = #student.classifieds.where(location: 'Malden').first
end
in view:
<%= #classified.ad_content %>

Form parameter isn't saving properly in Rails

So, I've got this action in order to save a model:
def create
logger.debug(params[:user_id])
group_id = params['approver']['group_id']
#approver = Approver.new(params[:approver])
#approver.user_id = params[:user_id]
respond_to do |format|
if #approver.save
logger.warn("Approver saved!")
flash[:notice] = "New approver has been added!"
format.html { redirect_to(group_path(group_id)) }
else
flash[:notice] = "Sorry .. had issues adding the approvers!"
format.html { redirect_to(group_path(group_id)) }
end
end
end
Parameters being passed in are:
Parameters: {"commit"=>"Submit", "authenticity_token"=>"C35lovRRjJzekruZiwTZjaMs4KgwiEJnXn10b0nD+0w=", "utf8"=>"✓", "user_id"=>["18"], "approver"=>{"group_id"=>"13"}}
And looking at my logs, the debug message in the action prints '13' as the correct value. However, the value being inserted into the database is always '1' and here's the snippet from the logs:
INSERT INTO `approvers` (`user_id`, `updated_at`, `created_at`, `group_id`) VALUES (1, '2011-07-13 04:58:51', '2011-07-13 04:58:51', 13)
To further complicate matters, in order to debug, if I change line 5 of the action to:
#approver.user_id = 19
it all works fine.
Can anyone explain what I'm doing wrong?
The group_id isn't your problem, that is going through fine. Your problem is this:
"user_id"=>["18"]
So params[:user_id] is actually an array but you're treating it like an ID. Then, someone inside Rails is converting that array to 1. When you say this:
#approver.user_id = 19
Everything works because you're assigning a Fixnum to user_id and that's what user_id expects.
You need to figure out why you're getting an array user_id and then fix that. Or, figure out what you should do with an array, maybe #approver.user_id = params[:user_id][0] makes sense, maybe not.

Resources