Increment integer on save in Rails 3? - ruby-on-rails-3

I have the following code in my Rails 3 application:
def like
#suggestion = Suggestion.find(params[:id])
Suggestion.update_all("votes = (votes + 1)")
redirect_to suggestions_url
end
def dislike
#suggestion = Suggestion.find(params[:id])
Suggestion.update_all("votes = (votes - 1)")
redirect_to suggestions_url
end
It's working, but rather than updating the current suggestion it's updating them all. So I changed it to:
def like
#suggestion = Suggestion.find(params[:id])
#suggestion.update_all("votes = (votes + 1)")
redirect_to suggestions_url
end
def dislike
#suggestion = Suggestion.find(params[:id])
#suggestion.update_all("votes = (votes - 1)")
redirect_to suggestions_url
end
but then I get:
undefined method `update_all' for #<Suggestion:0x007f87c2b918a0>
So then I tried #suggestion.update_attribute(:votes, '1') but that resets the value to 1 instead of incrementing it.
What's the correct way to achieve this? I just want the integer (votes) of the current suggestion to increment/decrease by 1 on each save.
I've also tried the following with no luck:
def like
#suggestion = Suggestion.find(params[:id])
#suggestion.increment(:votes)
redirect_to suggestions_url
end

This seems more suitable in the model. I suggest creating a like method inside the model like so:
def like
self.increment!(:votes)
end
Now you can do something like this:
#suggestion = Suggestion.find(params[:id])
#suggestion.like
Note: increment!, with the exclamation point also performs the save action

A couple things. It sounds like what you want is a controller action that increments an attribute by one. You were probably closest with the code
#suggestion.update_attribute(:votes, '1')
If you check the documentation for that method, it sets the value of the attribute votes to the second arguement, the string '1', on the object, #suggestion, and its corresponding row in the database. Instead of setting it to '1', you want to set it to the incremented value:
#suggestion.update_attribute(:votes, #suggestion.votes + 1)
Ethan suggested using the convenience method, increment!, which works just the same.
Now, if you wanted to actually auto-increment each time the object gets saved (as in something else about the object gets altered, you'd want to use the :before_save callback with increment without the bang.

Related

How to pass ActiveRecord model class to Resque

Let's say I have the following Resque job:
class Archive
#queue = :file_serve
def self.perform(repo_id, branch = 'master')
repo = Repository.find(repo_id)
repo.create_archive(branch)
end
end
What if I wanted to make this more generic by passing an object id and the object's class so that I can do something like this:
class Archive
#queue = :file_serve
def self.perform(object_class, object_id, branch = 'master')
object = object_class.find(object_id)
object.create_archive(branch)
end
end
This doesn't work, obviously, and I don't have a sense for what I should be doing, so if anyone can give some guidance, that would be really appreciated!
I would pass the name of the class to the job, rather than the class itself. Then you could call constantize on the name to get the class back, and call find on it. eg.
def self.perform(class_name, object_id, branch = 'master')
object = class_name.constantize.find(object_id)
object.create_archive(branch)
end

What's the best way to sanitize destroy_all - Rails

I have the following controller in Rails:
class FooController < ApplicationController
def delete_foo(bar):
Foo.destroy_all("foo = '#{#bar}'")
Is
Foo.destroy_all("foo = ?", #bar)
always valid?
destroy_all works on a relation. Why not do
Foo.where(foo: bar).destroy_all
Foo.destroy_all("foo = ?", #bar), This is invalid.
From apidoc, we will find:
destroy_all(conditions = nil) public
destroy_all method only accepts a single argument, the argument can be a string, array, or hash. You cannot pass two arguments.
So you can write like this:
Foo.destroy_all("foo = #{#bar}")
Foo.destroy_all(foo: #bar)
Foo.where(foo: #bar).destroy_all

Properties don't change when values are assigned?

I'm a bit of Ruby, noob, and there's some basic thing I'm not getting. I have something like this:
def my_method
attr1 = 'new 1 value'
attr2 = 'new 2 value'
puts "#{attr1} - #{attr2}"
puts "Should be same? #{to_s}"
end
def to_s
"#{attr1} - #{attr2}"
end
When I call my_method I get:
new 1 value - new 2 value
Should be same? old 1 value - old 2 value
Huh?
This is because in Ruby
x = y
is always an assignment of the value resulting from y to the variable x while
obj.x = y
is always sending the x= message to the object obj (with the value resulting from y).
In Ruby attributes/properties are really just methods! Thus, try:
self.attr1 = 'new 1 value'
self.attr2 = 'new 2 value'
On the other hand, y may or may not be a method call (read: property fetch). It depends on if there is already a variable y in scope or not because variables shadow methods. This is why attr1 and attr2 work in to_s without needing to be prefixed.
Happy coding.
There are two ways to do it. One is to use class-scoped variables instead of local variables:
class MyClass
def my_method
#attr1 = 'new 1 value'
#attr2 = 'new 2 value'
puts "#{#attr1} - #{#attr2}"
puts "Should be same? #{self.to_s}"
end
def to_s
"#{#attr1} - #{#attr2}"
end
end
m = MyClass.new
m.my_method
Output:
new 1 value - new 2 value
Should be same? new 1 value - new 2 value
The other way is to use attributes, which you have to specifically call as methods on self:
class MyClass
attr_accessor :attr1,:attr2
def my_method
self.attr1 = 'new 1 value'
self.attr2 = 'new 2 value'
puts "#{attr1} - #{attr2}"
puts "Should be same? #{self.to_s}"
end
def to_s
"#{attr1} - #{attr2}"
end
end
m = MyClass.new
m.my_method
This has the same output.
It's the scope attr1 and attr2 are local variables.
So when you're calling to_s it looking for attr_1 and attr_2 that you've declared (probably) in the class scope. Those won't get overwritten when you run my_method instead you just created a new variable in a smaller scope.
Try using #attr_1 and #attr_2 instead.
Check out Local Variable Gotchas

ROR Observer doesnt work

OrderItem observer doenst calculate total sum when updating OrderItem :((
what's wrong?
class OrderItemObserver < ActiveRecord::Observer
def after_save(order_item)
order_item.order.total_sum = order_item.order.order_items.collect{|i| i.price.to_i}.sum
end
end
listed in application.rb
config.active_record.observers = :order_observer, :order_item_observer
The result is being calculated then discarded as you are not saving the result.
class OrderItemObserver < ActiveRecord::Observer
def after_save(order_item)
order = order_item.order
order.total_sum = order.order_items.collect{ |i| i.price.to_i }.sum
order_item.save
end
end
Value is now saved. The order variable is just to tidy things up a little.

Flag invalid attribute in ActiveRecord

I am trying to implement functionality wherein an attribute, once set, cannot be changed on an ActiveRecord model. To this end, I have written the following methods:
def address
self[:address]
end
def address=(val)
if new_record?
self[:address] = val
else
errors.add(:address, "Cannot change address, once it is set")
return false # tried return nil here first, did not work
end
end
Am I doing something wrong here? I want the object to be invalid once I try to change an address, but I do not get any errors when I do obj.valid?
EDIT: The value is not changed once it is set, but I would like to get invalid object when I do the validation via obj.valid?
When you do obj.valid?, it clears all of your errors, and then runs each of the validations in turn. To have this produce an error on validation, you'll have to move that logic into a validation block.
Here's an example of one way to do that with an instance variable:
def address=(val)
if new_record?
self[:address] = val
else
#addr_change = true
return false # tried return nil here first, did not work
end
end
validate do |user|
errors.add(:address, "Cannot change address, once it is set") if #addr_change
end