undefined method error - ruby-on-rails

I edited this message do to sloppiness and changes.
def student_test
#student = Student.for_test.find(params[:id]) if params[:id]
#student ||= Student.new
run_sequence :testize
end
def test_finalize
Student.transaction do
if (params[:student]) and #student.update_attributes(params[:student])
#student.test!
end
room = Room.new(:room_num => 5)
room.save
book = #student.books
book.id_num = room.id
book.save
end
end
This returns this error message:
undefined method `id_num='
Is this because there is more than 1 book record being passed into book?
Any suggestions? Thanks.

Assuming Student has a has_many relationship to Book, then #student.books is returning an array of book records, so you'll need the following:
books = #student.books
books.each do |book|
book.id_num = room.id
book.save
end

You have horrible unreadable style. I tried to clear a little what I could.
def student_test
#student = Student.for_test.find(params[:id]) if params[:id]
#student ||= Student.new
run_sequence :testize
end
def test_finalize
Student.transaction do
if (params[:student]) and #student.update_attributes(params[:student])
#student.test!
end
room = Room.new(:room_num => 5)
room.save
book = #student.book
book.id_num = room.id
book.save
end
end
Your main problem was that named scopes are like finders - you don't write #student.find(:first), but write Student.find(:first). Same here - named scope is for retrieving object from DB, to add conditions ans rest to query. And then you call finder to get objects you wanted which.
I don't know flow of your program, but I suppose that test_finalize is run from student_test, so it can use #student.

Well, what Ruby is trying to tell you is that the Book class doesn't have an accessor to write to the id_num attribute. But since it seems likely that we're talking about Active Record here, and Book actually points to a database table -- I hate to suggest the obvious, but is id_num a real field in the books table?
Of course, the Ruby error may be entirely unhelpful, and something else is going on.
Perhaps you could show us a little more context? Where does this code live? What is the data structure? (Oh, and without wishing to be rude -- please consider indenting your code!)

Related

Undefined method in professors controller

I have a system that lets users or guests write a review. If users write a review it is associated with their user_id. If guests write a review they are asked to provide a username which is stored in a column called "guest" in the reviews database.
I'm not entirely sure how to do this but what I've done with the professor_controller is:
def show
#review = Review.where(professor_id: #professor.id).order("created_at DESC")
#avg_review = #review.average(:hw)
if #review.user_id = !nil
#user = User.where(id: #review.user_id)
else
#user = #review.guest
end
render
end
However, this yields an error:
NoMethodError in ProfessorsController#show
undefined method `user_id=' for #<Review::ActiveRecord_Relation:0x007fed19228e28>
I was getting this error even before I put the if statement in. I had the same problem when my controller looked like:
def show
#review = Review.where(professor_id: #professor.id).order("created_at DESC")
#avg_review = #review.average(:hw)
#user = User.where(id: #review.user_id)
end
#review works fine so does #avg_review. The Reviews table has a user_id column and the Users table has an id column.
You are getting an ActiveRecord::Relation (a collection of Reviews), not a single instance of Review. You will need to do Review.where(professor_id: #professor.id).order("created_at DESC").first or Review.find_by_user_id(#professor.id) to return a single instance.
That said, it sounds like this relationship isn't modeled properly, or there's a better way to express what you want to do through other means. Can you route take in the id of a review as a param?
Your #review variable actually holds an ActiveRecord::Relation object, like it clearly says in the error message:
NoMethodError in ProfessorsController#show
undefined method `user_id=' for #<Review::ActiveRecord_Relation:0x007fed19228e28>
That's because where always returns a Relation, even if it finds only one
record.

Undefined method in controller - rails

I have a controller called blogger:
class BloggerController < ApplicationController
def home
end
def favoritePosts
#blogger = current_blogger
#favorites = #blogger.favorite_posts
end
def suggestedPosts
posts = Post.all
#suggestedPosts = posts.similar_posts
end
end
And in the Blogger model i have a method:
def similar_posts
title_keywords = Post.body.split(' ')
Post.all.sort do |post1, post2|
post1_title_intersection = post2.body.split(' ') & title_keywords
post2_title_intersection = post2.body.split(' ') & title_keywords
post2_title_intersection.length <=> post1_title_intersection.length
end
end
When i run the server its gives me an error:
undefined method `similar_posts' for #<Post::ActiveRecord_Relation:0x007fa365029760>
After searching on stackoverflow i tried def self.similar_postsbut it still gives the same error message. I also tried new in the controller like this #suggestedPosts = posts.new.similar_posts which still gives me the same error.
Any suggestions on how to overcome this?
You have 2 issues happening at the same time. The first is that you're using posts in your call, when you should be using something more like post.blogger. The specific object depends on what your intent actually is.
The second issue is that you're making the call to similar_posts on the association, not on an individual record. This can be resolved with a call to each on the association.
So, putting those together and looking at what you might have meant, I think that you might have intended this as your suggestedPosts method:
def suggestedPosts
posts = Post.all
#suggestedPosts = posts.map {|post| post.blogger.similar_posts }
end
I also changed the name of #suggestedDevelopers to #suggestedPosts, because I don't think that you meant 'developers' in this case. This should give you something closer to what it appear you were trying for.

Rails association collection build if not exist

I have three models Exam, User and ExamResult. ExamResult contains the records for all the students (User) for exams (Exam). For one particular Exam record, there should be one record in ExamResult for each student. In the edit method of ExamController, depending if the ExamResult record has been created for one student, I need to build one new record or just skip it. Not sure if this is idiomatic Rails way of doing it.
# ExamController
def edit
User.students.each do |s|
#exam.exam_results.build(user_id: s.id) unless #exam.exam_results.find_by(user_id: s.id)
end
end
or this way:
def edit
newIds = User.students.map(&:id) - #exam.exam_results.map(&:user_id)
newIds.each do |id|
#exam.exam_results.build(user_id: id)
end
end
Maybe neither is idiomatic Rails. Any suggestions are welcome.
Edit
Bring find_or_initialize_by (recommended by #user3334690) on the table. If I understand the doc correctly, this should do the same as previous two implementations.
def edit
User.students.each do |s|
#exam.exam_results.find_or_initialize_by(user_id: s.id)
end
end
Instead of build you can use find_or_create_by_user_id in above case.
def edit
User.students.each do |s|
ExamResult.find_or_create_by_exam_id_and_user_id(exam_id, user_id)
end
end
there is this way:
def edit
User.students.each do |s|
ExamResult.where(exam_id: #exam.id, user_id: s.id).first_or_create
end
end
Use your second example:
def edit
newIds = User.students.map(&:id) - #exam.exam_results.map(&:user_id)
newIds.each do |id|
#exam.exam_results.build(user_id: id)
end
end
As I said in comments above this will query the database twice regardless of the number of students which will scale much better. If you're in a really extreme environment you can write it to use only one query along with "pluck" to pull only the "id" column (and avoid the object creation overhead) like this:
newIds = User.students.where("users.id not in (select user_id from exam_results where exam_id=?)", #exam.id).pluck(:id)
However, the readability is reduced for that. Your original would also benefit from using "pluck" instead of "map".
One other style note - I would use "new_ids" which is the standard idiomatic way of doing it with Rails.

Rails service objects

I'm using service objects to abstract a stripe payment feature into it's own class. I'm using this method https://gist.github.com/ryanb/4172391 talked about by ryan bates.
class Purchase
def initialize(order)
#order = order
end
def create_customer
customer = stipe create customer
if customer
charge_customer(customer)
else
stipe error in creating customer
end
end
def charge_customer(customer)
if charge is sucessfull
update_order(charge details)
else
stripe error in charging card
end
end
def update_order
#order.update(payment attributes)
end
end
Then in the order controller i'm doing something like
def create
#order = Order.new(params[:order])
if #order.save
payment = Payment.new(#order)
else
render 'new' with flash message "payment error"
end
end
My question is, how do i get the stipe error messages("stipe error in creating customer" and "stripe error in charging card") to display to the user?
Or can i call a service object in the order model and add it to order error messages? For example,
Order controller
#order.save_with_payment
Order model
def save_with_payement
payment = Payment.new(self)
#update order
self.payment_token = payment.token
etc
end
If i can do that with the model, how to i make a validation that shows the stripe errors?
Thanks in advance.
First of all try to separate concerns as possible. It already feels like Your Purchase/Payment class is doing too much, probably it should delegate part of it's routines to other service objects.
Second, I agree with phoet. I don't see the reason why You wouldn't pass params hash to service object. In our latest project we fully rely on service objects we call Factories to produce/manipulate our model objects. In Your case You could do like this:
class OrderFactory
def self.create(params)
new(params).create
end
def initialize(params)
#order = Order.new(params[:order])
end
def create
#payment = Payment.new(self)
#order.payment_token = payment.token
#....
end
end
Talking about validations - You can add validation methods to plain Ruby objects. For example by using ActiveModel:
http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/
Then You can create custom validators, like Ryan suggested.
Or You can use a gem like Virtus and add some custom validation rules.
i think that the service should be responsible for handling all this. so it might look like this
def create
#payment = Payment.new(params[:order])
if #order = #payment.execute_transaction
[...]
else
[...]
end
end
so the payment service would handle creating and persisting the order and also might be adding validation errors. you could also return some kind of #result object that might consist of the order and depending error messages.
I asked a very similar question just one day ago. The response I received was very helpful indeed (credit to #gary-s-weaver) so take a look.
Rails generic errors array
I went with the rescue_from approach in the end and it works well for me. Let me know how you get on as I'm very interested in this area. And if you need any code samples give me a shout.
I've written a gem for service objects. Check out peafowl gem.

Understanding SQL code in a controller in rails 3.2

I am new to ruby on rails and was going to some of the code already existing in the application.
The code is as follows(books):-
def index
#books = Book
#books = #books.select("books.*,
(select count(*) from book_issues where books.id = book_issues.book_id and book_issues.return_date is null) as issued_cnt,
(select count(*) from book_holds where books.id = book_holds.book_id) as hold_cnt")
#books = #books.joins("inner join book_issues on book_issues.book_id = books.id")
#books = #books.where('book_issues.return_date is null')
#books = #books.group('books.id')
#books.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #books }
end
end
I am finding this a little difficult to understand.Why is this code being used and why not use the below code:-
def index
if params[:book_id]
#book = Book.find(:all,
:conditions => ["book_id = ? ", params[:book_id] ],
:order => "action_date ASC")
end
end
Can someone please help me with this.
Read manuals and tutorials about associations and scoping in rails.
After that you should rewrite the code to something like this:
#model
class Book < ActiveRecord::Base
# Association for BookIssue, the BookIssue model should have a 'belongs_to :book'
has_one :book_issue
# Association for BookHold, the BookHold model should have a 'belongs_to :book'
has_one :book_hold
# Scope to get not returned books, it joins all issues that don't have a return date.
# All book with a return date will be ignored.
scope :not_returned, joins(:book_issue).where(:book_issues => { return_date: nil } )
end
#controller
def index
# Use the scope mentioned in the model, to get all not returned books.
#books = Book.not_returned.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #books }
end
end
The primary problem being solved here is "generate an array of books which aren't currently checked out and pass it to the template engine to render on the page". That SQL code is handling the first part. It's unfortunate that you have to join against book_issues to see if there are any available copies, but disregarding that for right now you'd want to define a method on Book like :available? that returned true when there's at least one copy not checked out and then use that in your controller.
As a further adjustment, I'd like to have a database column on the book records that let me know if they were available for checkout without joining against the book_issues table (BooksController#Index sounds like it'd be invoked an awful lot, and you don't want it to thrash the database). That'd mean updating your book checkout logic to tweak the master book record, maybe.
The code would be much happier if it looked like this:
#books controller
def index
#books = Book.available
respond_to ... # continue as before
end
# book model
scope :available, where(copies_available: true)
# book_issue model
belongs_to :book
after_save :update_parent_availability
def available?
return_date.nil?
end
def update_parent_availability
book.copies_available = book.issues.select(&:available?).any?
book.save if book.changed?
end
That :update_parent_availability action might be subject to race conditions. You should probably factor it out into a helper book availability management class and run it in a transaction.
I think you'll find the ActiveRecord sections of the Rails guide very helpful. I'd suggest giving the ActiveRecord querying docs a thorough read. Pay close attention to the general style of the examples. One of the most powerful aspects of the MVC (Model-View-Controller) pattern is that you can build very simple interfaces within your model that do the "heavy lifting" rather than cluttering up your controllers with logic that really doesn't belong there.

Resources