creating has_one association error Rails 4 - ruby-on-rails

I'm trying to create and order that is associated with an item.
An Order has one item:
class Order < ActiveRecord::Base
has_one :item
end
An Item belongs to an order:
class Item < ActiveRecord::Base
belongs_to :user
end
According to the guide this should work:
build_association(attributes = {})
create_association(attributes = {})
I have this in my controller:
def create
#order = #current_item.build_order(order_params)
#order.save
redirect_to #order
end
And this is the error I'm getting:
undefined method `build_order' for nil:NilClass
I know this has to do with how I've defined current_items but I've tried many different things and all lead to this same error message.
I have this in my application helper:
def current_item
Item.find(params[:id])
end
Can anyone point me in a better direction for how to define this or what I'm doing wrong here. Thanks for your help!

1) You don't have access to a helper method from the controller. You can include the helper class in your controller but it's a really bad practice. You must use helper methods only in the views.
2) You can move current_item method from the helper to the controller. Then there will be another problem. In your create method, you are trying to access instance variable #current_item which is not initialized at the moment, not the method. You can do it this way:
#order = #current_item.build_order(order_params)
to
#order = current_item.build_order(order_params)
Then current_item will return you Item object.
3) I am not sure what are your params, but you can implement it this way:
def create
#order = Order.new(params[:order])
#order.save
redirect_to #order
end
where params[:order] is for example:
{name: "order 1", item_id: 1}

You should change your create to use a method, rather a variable, so modify it as follows:
def create
#order = current_item.build_order(order_params)
#order.save
redirect_to #order
end
# rest of code
def current_item
Item.find(params[:id])
end
This should help.
Good luck!

The error you're getting is being caused by trying to run Item.find(params[:id]) but not passing it a valid value. It seems that params[:id] is maybe nil? Can you confirm this using a debugger or by temporarily adding raise "Params[:id] is set to #{params[:id]} to the first line of the method, running the code and seeing what it says in the terminal output?
All you need to do make this work is have a parameter value for the item come from the form that is being submitted. Normally rails uses the route/url to populate the value of params[:id]. For example, when the request is GET /items/1, params[:id] is 1.
In this case though, unless you've done some custom routing that you haven't shown in your question, creating a new order would usually be a POST to /orders and since there is no id in the url, params[:id] is nil.
It's up to you to add the item id from the order form. It would make sense that it would be sent with the rest of the order params as item_id, rather than just id, since id is usually used to reference the current object, which is a new order and therefore doesn't get have an id.
You'll need to make sure that item_id is whitelisted in your strong params with the rest of the values in the order_params method (I assume you defined this in the same controller but did not show it in the code), and then the code would look something like this.
def create
#order = current_item.build_order(order_params)
#order.save
redirect_to #order
end
#note the changes the the argument
def current_item
Item.find(order_params[:item_id])
end
def order_params
params.require(:order).permit(:item_id, :other_values_that_you_send)
end

Related

Trying to increment model attribute - Undefined method on model attribute

I'm trying to increment the value of a column in my model when a link is clicked and i keep getting an undefined method error on the attribute and i have no idea what i'm doing wrong
model
class VisitorsController < ApplicationController
def inc_adviser
self.adviser = self.adviser + 1
self.save
redirect_to root_path
end
end
route
match '/adviser' => 'visitors#inc_adviser'
view
<%= link_to 'Adviser', '/adviser', method: :post %>
Can anyone suggest what the problem is?
Thanks
self in that context is the controller instance, not the model instance you seem to expect it to be. You need to get a model instance from somewhere and increment the counter on that:
def inc_adviser
# Something like this, don't have enough information to be more specific.
model = WhateverModel.find(params[:id])
model.adviser = model.adviser + 1
model.save
redirect_to root_path
end
That is, of course, subject to race conditions so you should use increment_counter instead:
def inc_adviser
WhateverModel.increment_counter(:adviser, params[:id])
redirect_to root_path
end

Rails NoMethodError undefined method `data' for nil:NilClass (Controller#update)

Edit: it turns out I made a very simple mistake and had a Template that was associated with a LocalTemplate id that no longer existed. If anyone has this problem and thinks that they somehow are unable to unable to associate the id of another model in their update action, make sure that you didn't accidentally delete the parent object causing that id to no longer exist!
The code below, while dramatically simplified did work for me.
I have a Template model in my rails app. It has a method "data" defined in it.
I am able to access this method in the create and show actions with #template.data, however when using the same #template.data in the update action of my controller I get a no method error because I am not showing the correct local template id to it. This line can be found in the model where it reads base_data = YAML.load(local_template.data)
I stored an id of the associated local_template when initially saving a new template, but how can I make sure I reference that id again in the update action so I do not get a no method error?
Here is a simplified version of the Template model and controller
Model:
class Template < ActiveRecord::Base
def data
base_data = YAML.load(local_template.data)
# couldn't pass the correct LocalTemplate here because
# the local_template_id I had in my Template model no
# longer existed. Changing the id to a LocalTemplate
# that did exist fixed the issue.
end
end
Controller:
class TemplatesController < ApplicationController
def index
#business = Business.find(params[:business_id])
#templates = #business.templates.all
end
def new
#business = Business.find(params[:business_id])
#local_templates = LocalTemplate.all
#template = #business.templates.build
end
def create
#business = Business.find(params[:business_id])
#local_templates = LocalTemplate.all
#template = #business.templates.build(template_params)
if #template.save
#template.data #works fine here
redirect_to business_url(#template.business_id)
else
render 'new'
end
end
def show
#business = Business.find(params[:business_id])
#template = #business.templates.find(params[:id])
#template.data #works fine here too
end
def edit
#business = Business.find(params[:business_id])
#local_templates = LocalTemplate.all
#template = #business.templates.find(params[:id])
end
def update
#business = Business.find(params[:business_id])
#template = #business.templates.find(params[:id])
if #template.update_attributes!(pass_template_params)
Api.new.update_template(#template.data.to_json) #this is where I had a problem
redirect_to business_url(#template.business_id)
else
render 'edit'
end
end
end
You are mixing a lot. There is a lot to refactor in your controller...
First of all, your TemplatesController should be about the template resources, but your controller looks more like a BusinessesController. In general your update action for example should look more like:
def update
#template = Template.find params[:id]
#template.attributes = template_params # though this should raise a NoMethodError, because you dind't define it; I'd prefer params[:template] if possible
if #template.save
redirect_to business_url(#template.business_id)
else
#local_templates = LocalTemplate.all
render 'edit'
end
end
Instantiating #business and #local_templates makes non sense, because you don't use it at all. Speed up your responses if you can! :)
Fixed that, there is no need for the overhead of a nested resource in update (as you did).
If saving #template fails for validation reasons, you better should load the business object late by:
#template.business
in your /templates/edit.html.erb partial. Then you also do not need a nested route to your edit action... You see, it cleans up a lot.
As a general guideline you should create as less as possible controller instance variables.
If you cleaned up your controller and views, debugging your data issue will be easier.
I assume:
local_template
in your Template model to be an associated LocalTemplate model object. So it should no issue to call that anywhere if you ensured the referenced object exists:
class Template < ActiveRecord::Base
def data
return if local_template.nil?
YAML.load(local_template.data)
end
end
or validate the existence of the local_template object. or even b
You should confirm #template is not nil, if #template is nil, you can't use data method.
1.9.3-p547 :024 > nil.data
NoMethodError: undefined method `data' for nil:NilClass
from (irb):24
from /Users/tap4fun/.rvm/rubies/ruby-1.9.3-p547/bin/irb:12:in `<main>'
And you should use update_attributes!, it can raise an exception if record is invalid.
You can do like this.
if #template
#template.update_attributes!(template_params)
#template.data
end

How to create controller actions/methods(best practices/common language)

I am rather new to rails and programming in general. I feel I have picked up the rails MVC and other concepts pretty well but still have a hard time figuring out the syntax of what goes into controller actions. For example when you create a
def edit
end
How do you know how to format the contents/inside of that method.
Thus far I have seen alot of this:
def new
#product = Product.new
end
If I understand this correctly this is creating an instance of the Product Model and putting it into an instance variable that is accessible by the "new" view in products/view
But let's say I want to edit that. My inclination is to do add the following action in the controller:
def edit
#product = Product.edit
end
I'm not sure the syntax Product.edit is correct though, not sure how to differentiate between edit and update either. How do I know what calls on my Model Object when creating instance variables? Is there somewhere online I can go to learn this? I have found no where thus far with a good list of commands I can play with.
def edit
#product = Product.edit
end
should be
def edit
#product = Product.find(params[:id])
end
simple explanation
The edit action (#method) is called when you call e.q localhost:3000/products/1/edit
the 1 is the id of your product which is passed to your controller and can be accessed by using params.
when the user hit edit . It is ussually send the data to update action
def update
#product = Product.find(params[:id])
#product.update(params[:product].permit(:title, :desc))
end
Ok i know i'm not explain it good enough. You really need to read this
http://guides.rubyonrails.org/
To edit something, first you need to have (or get, or create, or etc...) it. In the new method, you just create new instance of Product, this is not necessary, but needs for *form_for* helper, and generally good practice, because it you can use same form for creating and editing. Product.new just creates new product and initialize its fields with default values. Product.find searches product (single) in database by id and returns it. So for editing you first need to find your product, then it will be used to fill fields in editing form, and than in update method you will update it:
def update
target_product_required
#product.assign_attributes(product_params)
if #product.save
redirect_to #product
else
render :edit
end
end
def target_product_required
#product ||= Product.find(params[:id])
end
def product_params
params.require(:product).permit(:title, :description, :price, :available_quantity, :image, :remote_image_url)
end
This is common pattern: target_product_required returns/assigns founded by id in params product to instance variable, product_params returns product specified params. More about this read in http://guides.rubyonrails.org/

Add Session User Id Into Line_Items table when line item record is created - Ruby on Rails

I am trying to add User Session ID into Line_Items table. I have researched everywhere and couldn't find the answer and that is why had to post it here. This application successfully adds the Phone ID and the Cart ID into the Line_Items table but doesn't let me add user session ID when I try the similar method of Phone and Cart ID. See below for my current implementation:
Line_Items Controller:
def create
# current_cart method in application_controller.rb
#cart = current_cart
phone = Phone.find(params[:phone_id])
if phone.stock > 0
#line_item = #cart.add_phone(phone.id )
if #line_item.save
redirect_to :back, notice: " '#{phone.model}' has been added to your cart."
else
render action: "new"
end
else
redirect_to :back, notice: " Cannot add: '#{phone.model}' - stock level is too low."
end
end
Cart.rb Model:
def add_phone(phone_id)
current_item = line_items.find_by_phone_id(phone_id)
if current_item
current_item.quantity += 1
else
current_item = line_items.build(phone_id: phone_id)
current_item.quantity = 1
end
current_item.phone.stock -= 1
current_item.phone.save
current_item
end
# returns true if stock level is greater than zero
def can_add(phone)
phone.stock > 0
end
Please let me know if any further code needed.
The issue is that the model classes in Rails don't know anything about the controller nor the session. I'm presuming you are using some authentication mechanism that gives you a current_user in your session... so we have to get that into your add_phone method.
In the controller, I'd change this line:
#line_item = #cart.add_phone(phone.id)
to this:
#line_item = #cart.add_phone(phone, current_user)
(notice there are two changes there - one to pass the phone rather than its id, since you already found it, and one to pass the current user.
Then we want to change your add_phone method to look like this:
def add_phone(phone, current_user = nil)
# make sure your Item class initializes quantity to o, not nil.
current_item = line_items.find_or_initialize_by_phone_id(phone.id)
current_item.increment!(:quantity)
current_item.phone.decrement!(:stock)
current_item.user = current_user
current_item.phone.save
current_item
end
notice I'm setting the user to nil by default... that way, code you already have that doesn't provide that field will continue to work. I'm also simplifying your method a little bit with the find_or_initialize_by helper... avoids an 'if' test. Also, the increment and decrement helpers clean up the intent of the code a little.
You should make sure your line item class includes
belongs_to :user
If you find yourself in a situation where you need to know current_user for a lot of domain logic, you might want to check out the sentient_user gem.
Save
There could be a number of reasons why your user ID is not being inserted:
- Is `user_id` variable accessible from your model?
- How are you saving `user_id`?
- Where are you defining the `user_id` variable?
If you're using an instance method (like in Cart.rb) and something like Devise, you likely won't be able to access the current_user helper directly; you'll have to pass it through the method:
def add_user(user_id)
#user = User.find(user_id)
#etc
end
If you post errors or other code pertaining to the user object directly, we'll be in a much better position to help!
Refactoring
I think you'll benefit from the increment! & decrement! methods:
def add_phone(phone_id)
current_item = line_items.find_by_phone_id(phone_id)
if current_item
current_item.incremenet!(:quality)
else
current_item = line_items.build(phone_id: phone_id)
current_item.quantity = 1
end
current_item.phone.decrement!(:stock)
current_item.phone.save
current_item
end

Getting "undefined method for Nil Class" error when calling the reciprocal model method

I currently have two models School and Course where School has_many courses, and Course belongs_to school. Additionally, School and Course are nested resources, where School is the parent resource, and Course the child.
I have created several test records in the Rails Console so that a query such as when the child calls upon the parent Course.first.school successfully executes and returns all the relevant information of the school Course.first is associated with.
However, when put into a controller function, I would instead get an error "undefined method `school' for nil:NilClass" for the following line:
redirect_to school_course_path(#course.school, #course)
.. as if the .school part wasn't recognized (where as it was in the console). Why is this the case, and how do I get past this error? Thanks!
Edit - as suggested, it could be that my #course instance variable isn't passed from method to method in the controller. I have attempted at passing them through via a private method, but its still giving me the same error. Here is my code (background: the model Question belongs_to Course, with Course having many questions. Course isn't part of the nested routes)
class QuestionsController < ApplicationController
def new
#course = Course.find(params[:course]) #confirmed working
self.current_course = #course #I attempt to set current_course, a private method
#question = Question.new
end
def create
#question = Question.new(params[:question]) #also works, in rails console all the questions confirms to have rails id
if #question.save
redirect_to school_course_path(current_course.school, current_course) #source of my frustrations - continues to returns same error message
else
render 'new'
end
end
private
def current_course=(course)
#current_school = course
end
def current_course
#current_course
end
end
Should work if your relationships are set up the way I think they are:
def create
#question = Question.new(params[:question])
#course = #question.course
if #question.save
redirect_to school_course_path(#course.school, #course)
else
render 'new'
end
end
Make sure you have something like this in your create action:
#course = Course.new(params[:course])
your code is okay, it seems there is problem in your redirect.. redirect it to root_path and check whether it is working??

Resources