Rails loses change tracking when calling instance method - ruby-on-rails

When changing a model's attributes in the controller's update, I want to generate a history of changes that are about to be made. For that, I've created a method generate_history to access from the instance.
The class:
class Assist < ActiveRecord::Base
belongs_to: status
def generate_history
#Also tried without self
p 'Testing round two'
p 'Status' + self.status.id.to_s
p 'Modified: ' + self.status.id_changed?.to_s
p 'Old value ' + self.status.id_was.to_s
#do something something dark side
end
end
The issue is within that method, the ActiveModel::Dirty isn't aware of the changes that have been made, although the self.status_id's value is the new one.
Meanwhile in the controller:
def update
...
#assist.assign_attributes(assist_params)
p 'Testing round one'
p 'Status' + #assist.status_id.to_s
p 'Modified: ' + #assist.status_id_changed?.to_s
p 'Old value ' + #assist.status_id_was.to_s
p 'Generating history'
#assist.generate_history
p 'Testing round three'
p 'Status' + #assist.status_id.to_s
p 'Modified: ' + #assist.status_id_changed?.to_s
p 'Old value ' + #assist.status_id_was.to_s
end
At first I was suspicious of assign_attributes that somehow interfered with ActiveModel::Dirty but I've realized that ActiveModel::Dirty works in the controller, where the values are being modified, but not when I'm calling generate_history.
Am I doing something wrong from within the instance method or its the way ActiveModel::Dirty works?
Example of output:
Testing round one
Status 1
Modified: true
Old value 2
Generating history
Testing round two
Status 1
Modified: false
Old value 1
Testing round three
Status 1
Modified: true
Old value 2

My guess is that the status_id is not an attribute that is fully tracked by ActiveRecord::Dirty. Instead of looking at status_id, i think you should look at status. Something like:
class Assist < ActiveRecord::Base
belongs_to: status
def generate_history
p 'Testing round two'
p 'Status' + status_id.to_s
p 'Modified: ' + status_id_changed?.to_s
p 'Old value ' + status_id_was.to_s
#do something something dark side
end
end
Then your controller code is right.
And because you're not assigning status but only asking about it, you probably don't need the self.

Related

How can you set the order of the items in a nested array for an Active Record object?

Here's the code I have:
def show
#tournament = Tournament.find(params[:id])
#tournament.games = #tournament.games.order(sort_column + ' ' + sort_direction)
puts #tournament.games.inspect
end
I'm trying to sort the games in the tournament. If I do puts #tournament.games.order(sort_column + ' ' + sort_direction) it returns the games in the correct order. But I can't seem to actually change #tournament.games. I've tried the assignment, no assignment, order!, order without the !, and nothing seems to be working.

Simple calculation not working before save, Rails

I've got a base_price field, a shipping_price field, and a total_price field.
It's a simple base_price plus shipping_price equals total_price. For some reason, I cannot get this simple calculation to work when saving my model.
Here's my code:
item.rb
before_save :total_price_calculator
private
def total_price_calculator
self.total_price.to_i = self.base_price.to_i + self.shipping_price.to_i
end
It's failing to make the calculation and save it in the database and I'm not really getting an error as to why.
Try to the following
self.total_price it automatically saves integer when assigned to this any value integer, that's why you don't need to call to_i
After modified
before_save :total_price_calculator
private
def total_price_calculator
self.total_price = base_price.to_i + shipping_price.to_i
end
You don't need to use self on base_price and shipping_price because that is are already have values which given from form.
Or you can use directly looks like below
before_save {self.total_price = self.base_price.to_i + self.shipping_price.to_i}
Hope it helps
You are applying a method on an assignment here and I suspect that this might be the issue, try it like this:
def total_price_calculator
self.total_price = self.base_price + self.shipping_price
end
total_price is going to be an int anyway
Try using another methods like before_update/create/validate, inserting breakpoint too see what happens there, returning false to cancel all following callbacks.
Perhaps this is relatable:
Rails: How to use before_save to change a field value based on another field?
try
def total_price_calculator
self.total_price.to_i = self.base_price.to_i + self.shipping_price.to_i
true
end
sometimes if it comes out as nil, that returns false for your validation which will rollback the record and not save.
Or to further debug try...
def total_price_calculator
puts self.base_price.to_i
puts self.shipping_price.to_i
puts self.base_price.to_i + self.shipping_price.to_i
puts self.total_price.to_i = self.base_price.to_i + self.shipping_price.to_i
self.total_price.to_i = self.base_price.to_i + self.shipping_price.to_i
true
end
if the last puts returns false or nil, your save will fail validation and not save.

ruby about attr_accessor, instance variables, local varibles

I'm so confused about that..
like this
class Box
attr_accessor :item ,:item2
def initialize(item2)
#item = []
#item2 = item2
end
def add(product)
item << product
end
def empty?
item.empty?
end
def increment(n=1)
item2 +=1
end
end
cart =Box.new(123)
cart.add(1)
puts cart.empty? #false
puts cart.item #1
in the 'add' and 'empty?' methods
I use local variable 'item' right?
why I can get the value from #items ??
and I try this
cart.item2 = 345
puts cart.item2 #345
puts cart.increment #'increment': undefined method `+' for nil:NilClass (NoMethodError)
now I can't get the value?
please fix my brain thx
First, read this answer, which is the most-upvoted Ruby post in StackOverflow history. It will help you understand attr_accessor and its cousins attr_reader and attr_writer.
Besides that, your code has many problems.
First, you should not name an Array with a singular variable name like item. Use a plural items to make its purpose clear.
Second, the name item2 is not good. For your attribute, use something descriptive like counter, and for the variable passed as an argument to initialize it, let's use something descriptive like initial_count.
Third, your increment method takes an optional argument but then ignores it. Wouldn't it be surprising if someone called box.increment(2) and the attribute was incremented by only 1? The intent of this method is to use counter += n instead of counter += 1.
Fourth, to set counter from within the class, we need to use self. So instead of counter += n, we have to do self.counter += n.
Finally, consider whether you want the attributes to be readable and writable from an outside source, or whether you want to reserve write privileges to the object itself. Because you have methods to add things to items and to increment counter, you probably want to conceal write privileges. I would use attr_reader publicly and attr_writer privately.
Incorporating these suggestions, here's the resulting code:
class Box
attr_reader :counter, :items
def initialize(initial_count)
#counter = initial_count
#items = []
end
def add(product)
items << product
end
def empty?
items.empty?
end
def increment(n = 1)
self.counter += n
end
private
attr_writer :counter, :items
end
Now you can do this, all of which makes sense, more or less:
>> cart = Box.new(123)
>> cart.increment(2)
>> cart.counter
#> 125
>> cart.add('A product')
>> cart.add('Another product')
>> cart.items
#> ["A product", "Another product"]
But if you try to set counter or items directly, you'll get an error:
>> cart.counter = 1
#> NoMethodError: private method `counter=' called for #<Box:0x007fc13e17dc50>

Writing conditionals in rails

I need to write a conditional in order to find users without facebook id's and then have a image path set for those users. Here is the code so far.
# load rails
require '../../config/boot.rb'
require '../../config/application.rb'
Rails.application.require_environment!
users = User.order('id ASC').limit(500)
users.each do |user|
facebook_id = user.facebook_id
unless facebook_id.blank?
puts facebook_id.size
end
end
I was thinking maybe an if/then conditional so if the number is 0 (meaning blank) then input https://graph.facebook.com/ + facebook_id + '/picture?width=120&height=120 for that user.
Maybe something like:
if facebook_id.blank
then input.('https://graph.facebook.com/' + facebook_id + '/picture?width=120&height=120')
For the query part this is the way to go:
User.where(facebook_id: nil) #=> returns all users without facebook_id
About the path, you were close:
class User < ActiveRecord::Base # reopen User class and define a new instance method
def poster_path
if self.facebook_id
"https://graph.facebook.com/" + self.facebook_id + "/picture?width=120&height=120"
end
end
end

Modifying the default return value of an object belonging to an extended class

I am relatively new to the rails syntax. I have a LogString class
class LogString < Array
I do the following with it
logs = LogString.new
logs.push 'this happened'
logs.push 'that happened'
which works fine. i want to be able to just write only
logs
to return what would be logs.join( ' | ' )
so i am looking for a syntax something like the method log_string here
class LogString < Array
def log_string
self.join( ' | ' )
end
end
but where log_string is automatically called when i simply write the class instance name: logs
how can i do that?
You can't just reference an object and have it call a method on the object, but you can get pretty close.
If you override the to_s method (short for "to string"), you'll be able to do something like...
class LogString < Array
def to_s
self.join ' | '
end
end
log = LogString.new
log << "message one"
log << "message two"
puts "#{log}"
Add this:
def to_s
join '|'
end
This will work in templates and some I/O ops where #to_s is called. It won't work in irb unless you also modify #inspect. (You could just have it call your new #to_s.)

Resources