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
Related
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
If I want to decrement the counter attribute of an object, is it better to add it to the update action, or the destroy action, or is it better to create a new action?
To explain better, let's say you have a Product and a CartItem class, and every time a user adds the same product to her cart, instead of having two copies of the same CartItem object, you increment the amount attribute of the CartItem.
If I want to let users decrement the amount, what should I use? For example, is something like the code below a viable and conventional way of doing this? Or is it better to create a new action?
View:
...
<%= button_to 'Remove One', [cart_item, remove_one: true], method: :patch %>
...
Controller:
def update
if params[:remove_one]
if #cart_item.quantity > 1
#cart_item.decrement!(:quantity)
flash[:notice] = "Removed one of #{#cart_item.product.title}"
redirect_to :back
else
destroy
end
end
return
end
Note: I understand that the title sounds a bit misleading, but I am not looking for a way to decrement an attribute in Rails. What I want to know is, if you have such a requirement, which action do you put that in? How do you decide what goes in which action?
Edit: Guys I really expected something credible like an example from a well-maintained source-code, a high-rated book, etc. Under these circumstances, unfortunately I won't be able to award the bounty to anyone.
I don't think there's a 100% correct answer but from my experience I see two options that are better than your code example:
Have a decrement action on the controller and in the controller you use the decrement_counter method (doc). This helps if you have concurrent updates on the same model (though that's not usually the case for shopping carts).
Have an update method and in the view render a small form:
The view
<%= form_for cart_item do |form| %>
<%= form.hidden_field :quantity, cart_item.quantity-1 %>
<%= form.submit 'Remove One'
<% end %>
In the controller
def update
#cart_item.update(cart_item_params)
#cart_item.destroy if #cart_item.quantity == 0
#in the above
redirect_to :back
end
def cart_item_params
params.require(:cart_item).permit(:quantity)
end
add a dedicated method in CartItem class...such as
##it will return either true or false
def !!update_amount
###use if condition if you need
if self.quantity > 1
self.update_attribute :amount,self.amount+=amount-1
end
end
use it anywhere you want such as:--
def update
if params[:remove_one]
##call the method to get only true or false
if #cart_item.update_amount
flash[:notice] = "Removed one of #{#cart_item.product.title}"
redirect_to :back
else
destroy
end
end
return
end
To me it make sense semantically to have this in a separate method.
You are updating an attribute on a model, which means that you should use the PATCH method, and preferably invoke the update method per rails' conventions, so in my opinon this separate method invocation should be performed without altering the RESTful routes.
before_action :inventory_check, only: :update
def update
if #cart_item.update
flash[:notice] = "Updated your cart."
else
flash[:error] = "Couldn't update your cart."
end
redirect_to :back
end
def destroy
#cart_item.destroy
flash[:notice] = "Item removed from your cart."
redirect_to :back
end
private
def inventory_check
if params[:decrement] && #cart_item.quantity == 1
redirect_to :destroy
end
end
Seems to me that you CartItem object describes the many-to-many relationship between Product and Card. So when you are adding a Product to a Cart, you are just updating (or creating if there isn't one yet) the content of that specific Production-Cart relationship.
In this sense, I would suggest you to just put the decrement in the update method. However, if you want to do some more logic to determine whether this relationship exists or not, you can also create a new method and do all the validations in it.
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
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??
I am using filter to set the primary key of an instance before saving it.
Here is my controller method:
class ReferencesController < ApplicationController
before_filter :set_primary_key, :only => [:create_sub_reference]
def create_sub_reference
#reference = Reference.new(params[:reference])
respond_to do |format|
if #reference.save
format.js
else
flash[:notice] = "Reference failed to save."
end
end
end
private
def set_primary_key
result = ActiveRecord::Base.connection.execute('SELECT REF_ID FROM SEQUENCES')
inc_result = (result.fetch_row.first)
self.REF_ID = inc_result
end
end
end
I am getting the following error message in the log file when i click on the 'Save button':
NoMethodError (undefined method `REF_ID=' for #<ReferencesController:0xb69f4ca8>):
Thanks for any suggestion on this matter
You're trying to set the REF_ID attribute - which I assume is a database column - on your Controller, not your model. That code will be invoked every time a web request for ReferencesController reaches your app.
Perhaps you wanted to move the logic to a before_create hook in the References model?