I seem to remember reading somewhere that rails won't commit to the database if no attributes have been changed (presumably as part of active record dirty) is this the case? I can't seem to find it and am none the wiser having had a quick look through the source code.
If this is false I need to use a before_save callback because I want to run some code based on whether or not a change has occured.
I assume after_save with dirty data won't work??
Rails won't do an UPDATE but it will run the after_save callbacks even if nothing has changed:
pry $ u = User.first
=> #<User id: 1, email: "dave#davebryand.com", username: "dbryand"...
pry $ u.updated_at
=> Tue, 24 Jun 2014 18:36:04 UTC +00:00
pry $ u.changed?
=> false
pry $ u.save
From: /Users/dave/Projects/sample/app/models/user.rb # line 3 :
1: class User < ActiveRecord::Base
2:
=> 3: after_save -> { binding.pry }
pry $ exit
=> true
pry $ u.updated_at
=> Tue, 24 Jun 2014 18:36:04 UTC +00:00
If you want to do something dependent on changes to a particular attribute in a before_save, just do:
pry $ u = User.first
=> #<User id: 1, email: "dave#davebryand.com", username: "dbryand"...
pry $ u.save
=> true
pry $ u.name = "John Doe"
=> "John Doe"
pry $ u.save
From: /Users/dave/Projects/sample/app/models/user.rb # line 3 :
1: class User < ActiveRecord::Base
2:
=> 3: before_save -> { binding.pry if name_changed? }
Dirty docs here: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
Related
I am using the following versions:
ruby 2.5.5
rails 5.2.3
state_machines-activerecord 0.6.0
I have a model Foo that has a state machine on it:
class Foo < ApplicationRecord
def post_activate
puts "hello from sunny post_activate"
end
state_machine :state, initial: :initial do
state :active
after_transition any => :active, :do => :post_activate
event :activate do
transition initial: :active
end
end
end
I'm trying to write an rspec test to ensure post_activate gets called after the transition to state :active.
I can test ok from the Rails console:
2.5.5 :001 > thinger = Foo.new
=> #<Foo id: nil, state: "initial", created_at: nil, updated_at: nil>
2.5.5 :002 > thinger.activate
(0.1ms) begin transaction
Foo Create (0.4ms) INSERT INTO "foos" ("state", "created_at", "updated_at") VALUES (?, ?, ?) [["state", "active"], ["created_at", "2019-06-28 21:35:22.555917"], ["updated_at", "2019-06-28 21:35:22.555917"]]
hello from sunny post_activate
(0.7ms) commit transaction
=> true
However, when I run my test:
describe 'Foo' do
it 'should fire post_activate' do
foo = Foo.new
expect(foo).to receive(:post_activate)
foo.activate
end
end
I get the following error:
1) Foo should fire post_activate
Failure/Error: foo.activate
ArgumentError:
Wrong number of arguments. Expected 0, got 1.
Interestingly, another test runs correctly with no errors:
it 'should change the state' do
foo = Foo.new
expect{
foo.activate
}.to change {
foo.state
}.from('initial').to('active')
end
This test passes and prints my debug message.
I tried putting a parameter into the post_activate definition:
def post_activate(arg)
puts arg
puts "hello from sunny post_activate"
end
Now the test passes - and the puts statement reveals that the object passed in is a StateMachines::Transition
#<StateMachines::Transition:0x00007fd5d3534228>
hello from sunny post_activate
I'm not sure how this works - I know how to make a parameter optional, but I don't know how to optionally pass an argument.
In any case - the test passes if I add a parameter to my method definition. But I don't want to add a parameter I'm not using just to get my specs to pass. How do I resolve this?
RSpec verifying proxy in validate_arguments! getting call for post_activate with current transition. You can see it if you add something like byebug to your Gemfile, and add same byebug conditionally into validate_arguments! of Rspec:
#method_reference.with_signature do |signature|
verifier = Support::StrictSignatureVerifier.new(signature, actual_args)
byebug unless verifier.valid?
raise ArgumentError, verifier.error_message unless verifier.valid?
That will give you access to actual_args:
(byebug) actual_args
[#<StateMachines::Transition ...]
So to fix your problem, you just need to add omitted parameter to your callback (as you clearly already figure out):
def post_activate(_)
And to be sure why underscore used that way, take a look at Ruby Style Guide.
I am migrating code from Rails 3 to Rails 5.1.6 and Ruby 2.5. I am facing strange issue. I have written after_initialize callback in model and assigning default value to field. The field is serialize and when there is nil in that field it will assign default value inside the callback.
Here is my code for clear picture
class MyModel < ActiveRecord::Base
serialize :settings
after_initialize :set_default_setting
def set_default_setting
self.settings = self.settings || {my_fields: ["a", "b", "c"]}
end
end
Here is my Rails console log :
[1] 2.5.0 :135 > md = MyModel.find(1)
=> #<MyModel:0x00007fe433608348
id: 1,
settings: <ActionController::Parameters permitted: >,
created_at: Tue, 16 Jan 2018 08:45:26 GMT +00:00,
updated_at: Fri, 04 May 2018 10:50:20 BST +01:00>
[2] 2.5.0 :136 > md.settings
=> <ActionController::Parameters permitted: >
[3] 2.5.0 :137 > md.settings.nil?
=> false
My question is why <ActionController::Parameters permitted: > is being added in to nil field ? when I execute nil? method it should return me nil. Above code was working fine with Rails 3.2 and Ruby 2.0 from which I am upgrading to Rails 5 and Ruby 2.5
Any help will be appreciated.
Thank you in Advance.
I am using accessing my controllers methods from console with rails c command. The problem I am facing is that each time I have reflect any changes made in the code then I have to first exit and restart . Is these any way to fix this problem?
From your rails console, type reload!
2.1.2 :012 > reload!
Reloading...
=> true
2.1.2 :013 >
to reload all your Rails application code. No need to exit and start console again!
If you have associations you can do this:
class home
belongs_to :renter
end
class renter
has_one :home
end
Let's say you start with home attributes:
home = Home.where(renter_id: 1)
=> #< Home id: 1, alarm: "no">
renter = Renter.find(1)
renter.home.alarm
=> "no"
Then you modify home:
home.alarm = "yes"
home.save
When you do:
renter.home
=> #< Home id: 1, alarm: "no"> # it still returns no
renter.home(true)
=> #< Home id: 1, alarm: "yes">"
# you can use (true) to make sure your association
# change is reflected, it basically queries the server again
Suppose the following situation
class User < ActiveRecord::Base
private
def password= p
self[:password] = p
end
def password
self[:password]
end
end
If anyone with access to the Rails console can do:
Loading development environment (Rails 4.0.0)
2.0.0p247 :001 > User
=> User(id: integer, name:string, password:string)
2.0.0p247 :002 > u = User.find(1)
=> #<User id: 1, name: "Jack", password: "da6c253ffe0975ca1ddd92865ff3d5f0">
2.0.0p247 :003 > u.password = "123"
NoMethodError: private method 'password' called for #<User:0xa9145b0>
2.0.0p247 :004 > u[:password] = "123"
=> "123"
2.0.0p247 :005 > u
=> #<User id: 1, name: "Jack", password: "123">
2.0.0p247 :005 > u.save
=> true
Why does this happen? How can I encapsulate critical fields?
I am guessing that password is attr_accessible in the model. When a field is attr_accessible, Rails automatically lets you read and write to the field. You have a private password method that overwrites the Rails password and password= methods, but you did not overwrite the [] and []= methods as well. You can either overwrite the [] and []= methods or make it so password is not attr_accessible.
Here is a code example of how to overwrite the [] method:
class User < ActiveRecord::Base
def [](word)
puts "I am the master of: #{word}"
end
def []=(key, value)
puts "Fluffy monsters"
end
end
With this revised code, here is what the [] method will return:
>> u[:password] = "123"
=> nil
# prints "Fluffy monsters" in the console
>> u[:password]
=> nil
# prints "I am the master of: password" in the console
I'm building a json object in rails as follows:
#list = Array.new
#list << {
:created_at => item.created_at
}
end
#list.to_json
Problem is this gets received by the browser like so:
"created_at\":\"2000-01-01T01:31:35Z\"
Which is clearly not right, in the DB it has:
2011-06-17 01:31:35.057551
Why is this happening? Any way to make sure this gets to the browser correctly?
Thanks
You need to do some testing / debugging to see how that date is coming through.
For me, in Rails console (Rails 3.0.9, Ruby 1.9.2)
ruby-1.9.2-p180 :014 > d = Date.parse("2011-06-17 01:31:35.057551")
=> Fri, 17 Jun 2011
ruby-1.9.2-p180 :015 > #list = {:created_at => d}
=> {:created_at=>Fri, 17 Jun 2011}
ruby-1.9.2-p180 :016 > #list.to_json
=> "{\"created_at\":\"2011-06-17\"}"
i.e. it's just fine. Can you see whether the date is really ok?
The trouble lies with the way to_json escapes characters. There is a very good post on the subject here:
Rails to_json or as_json
You may need to look into overriding as_json.