50

How can I achieve this?

tried to create 2 methods, called

def disable_timestamps
  ActiveRecord::Base.record_timestamps = false
end

def enable_timestamps
  ActiveRecord::Base.record_timestamps = true
end

and the update method itself:

def increment_pagehit
  update_attribute(:pagehit, pagehit+1)
end

turn timestamps on and off using callbacks like:

before_update :disable_timestamps, :only => :increment_pagehit
after_update :enable_timestamps, :only => :increment_pagehit

but it's not updating anything, even the desired attribute (pagehit).

Any advice? I don't want to have to create another table just to count the pagehits.

Andrew
  • 2,829
  • 3
  • 22
  • 19
Kleber S.
  • 8,110
  • 6
  • 43
  • 69
  • Try using `update_attributes!(:pagehit => pagehit+1)` and see if you get any errors. BTW, did you paste the `def disable_timestamps` twice by mistake here, or is it the same in your code ? – Zabba Feb 03 '11 at 15:02
  • Possible duplicate of [Is there a way to avoid automatically updating Rails timestamp fields?](http://stackoverflow.com/questions/861448/is-there-a-way-to-avoid-automatically-updating-rails-timestamp-fields) – Dave Schweisguth May 12 '16 at 18:46

7 Answers7

103

As an alternative to update_attribute, In Rails 3.1+ you can use update_column.

update_attribute skips validations, but will touch updated_at and execute callbacks.

update_column skips validations, does not touch updated_at, and does not execute callbacks.

Thus, update_column is a great choice if you don't want to affect updated_at and don't need callbacks.

See http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html for more information.

Also note that update_column will update the value of the attribute in the in-memory model and it won't be marked as dirty. For example:

p = Person.new(:name => "Nathan")
p.save
p.update_column(:name, "Andrew")
p.name == "Andrew" # True
p.name_changed? # False
Nathan
  • 7,816
  • 8
  • 33
  • 44
  • 2
    The Mongoid equivalent of `update_column` is `set`, as in `person.set(:name, 'Andrew')`. [Mongoid Docs - Atomic Persistence](http://mongoid.org/en/mongoid/docs/persistence.html#atomic) – colllin Jan 13 '14 at 18:27
13

If all you're wanting to do is increment a counter, I'd use the increment_counter method instead:

ModelName.increment_counter :pagehit, id
idlefingers
  • 31,659
  • 5
  • 82
  • 68
3

it is not a good idea to do this:

self.class.update_all({ pagehit: pagehit+1 }, { id: id })

it should be

self.class.update_all("pagehit = pagehit + 1", { id: id })

the reason is if two requests are parallel, on the first version both will update the pagehits with the same number, as it uses the number saved in the Ruby memory. The second option uses the sql server to increase the number by 1, in case two of these queries come at the same time, the server will process them one after the other, and will end up with the correct number of pagehits.

Tom Caspy
  • 61
  • 2
2

To avoid Monkeypatchingtroubles you could also use ModelName.update_all for this purpose:

def increment_pagehit
  self.class.update_all({ pagehit: pagehit+1 }, { id: id })
end

This also does not touch the timestamps on the record.

fredostarr
  • 464
  • 1
  • 3
  • 9
2

You also have decrement and increment (and their bang versions) which do not alter updated_at, do not go trigger validation callbacks and are obviously handy for counters / integers.

tirdadc
  • 4,603
  • 3
  • 38
  • 45
0

If precision is not really that important, and you don't expect the code to run many times, you can try altering the saved in the database updated_at value, like so:

u = User.first
u.name = "Alex 2" # make some changes...
u.updated_at = u.updated_at + 0.000001.second # alter updated_at
u.save

so that Rails will actually try to save the same value, and not replace it with Time.now.

glampr
  • 379
  • 4
  • 6