I'm using the paper_trail gem. My use case involves creating versions when I explicitly want to instead of on callbacks like on: [:update] or something.
I did so by adding on: [] in my my_model.rb and using paper_trail.touch_with_version on my model instance. The problem is whenever I do this the first time the version is saved with nil attributes. Weird enough, the next time call paper_trail.touch_with_version it saves it correctly with all attributes correctly initialised.
Sample logs from my rails console:
document = Document.first
#<Document:0x0000123456
id: 1,
name: "Sample document">
document.paper_trail.touch_with_version
document.versions.last.reify.name
=> nil
document.paper_trail.touch_with_version
document.versions.last.reify.name
=> "Sample document"
What's really peculiar is that if I try doing document.paper_trail.touch_with_version followed by document.versions.last.reify.name I correctly get name of my document but if I exit console and repeat this process the first time the attributes of the object saved in that version is again nil.
I'm probably doing something obvious but didn't find anything on wiki that talks about this. Can someone explain to me where I'm messing up?
Update: I traced back the code in paper_trail library and found where the issue is. I found the anomaly in record_trail.rb:
# #api private
def attribute_in_previous_version(attr_name)
if #in_after_callback && RAILS_GTE_5_1
#record.attribute_before_last_save(attr_name.to_s)
else
#record.attribute_was(attr_name.to_s)
end
end
So in console the #record.attribute_before_last_save(attr_name.to_s) is giving me nil but if I try #record.attribute_was('name') in my console it gives me the correct attribute(in this case name) of the record.
Related
I am trying to implement 2FA(two factor authentication) in my existing rails 4.2.10 application, I have configured many bits.
Issue I am facing is to get/retrieve a code which is valid for 5 minutes and send this code over to user on his defined phone number or email.
I did tried ROTP::TOTP.new(user.otp_secret).at(Time.now), guessing from gem's source code, which seems to work fine and give a valid otp_code in console, but in sessions_controler, as weird as it sounds, user.otp_secret is null, always...
I have posted an issue on the gem.
I don't think this can be bug, rather this is a functionality I want to build.
My stack:
Ruby: 2.4.2
Rails: 4.2.10
Devise: 4
attr_encrypted: 1.4(if it matters)
Additionally, I want to extend drift period(code acceptance time) to 5 minutes. I think that will be easy, but doing it for single code, not universally, or for all codes, this has me thinking for a while now.
My main issue is the first one, getting the code to send through SMS, this is a subproblem, which I think is doable, but if anyone has/had experience with this and can help, that will be great.
UPDATE: I updated attr_encrypted and restarted the system, it started working, also I realized there is a method current_otp in which devise_two_factor adds in the user model, so I started using that. BUT after a few minutes, it is also throwing the same issue of user.otp_secret being nil. Its getting weird...
UPDATE 2/Hacky solution: Weirdly enough, I had to add these 3 methods in user model and everything started working:
def encrypted_otp_secret
self[:encrypted_otp_secret]
end
def encrypted_otp_secret_iv
self[:encrypted_otp_secret_iv]
end
def encrypted_otp_secret_salt
self[:encrypted_otp_secret_salt]
end
As you can suspect, i got here by examining a behavior thatdoing user.encrypted_otp_secret was giving me nil while it was not, even after reloading user model. And doing user[:encrypted_otp_secret] was giving me the actual value.
It seems like a bug in attr_encrypted. I am not sure yet.
For anyone else that runs into this issue, I have found a next step needed to get the current_otp method to work. In the method pre_otp method call
> u = User.find_by(email: 'test#example.com')
> u.otp_required_for_login = true
> u.otp_secret = User.generate_otp_secret
> u.save!
and then you can call u.current_otp...
https://blog.tommyku.com/blog/integrating-two-step-two-factor-authentication-into-rails-4-project-with-devise/
I am running through the Lynda Rails 3 tutorial. At one point, in a controller called access_controller, we call a method from a model called AdminUser. The original call was:
authorized_user = AdminUser.authenticate(params[:username], params[:password])
When I run rails server, open up the browser, and access the appropriate view, I get the error: TypeError, can't convert String into Integer
This same question has been asked twice before. The first time, the asker says the problem resolved itself the next day. (I first ran into this 3 days ago, so that has not happened.) The second question has not been answered. I will try to provide much more detail:
The method in the model was:
def self.authenticate(username="", password="")
user = AdminUser.find_by_username(username)
if user && user.password_match?(password)
return user
else
return false
end
end
When I call this method from the rails console, it works totally fine. Something about calling it from a controller, or trying to get at via the browser, seems to be going wrong (I am relative beginner, so I apologize that I cannot express this thought better). I have since replicated this error with a more simple method in the same AdminUser model:
def self.nothing
true
end
This still gives me the same error. I then tried calling the self.nothing method from a different controller and action (called pages_controller#show). When I tried to open that up in the browser, I once again got the same error: "can't convert String into Integer"
I then created an identical self.nothing method in my Subject model. When I try to run that method from the show action in pages_controller, it works totally fine. No errors.
So, the same method runs totally fine in rails console, totally fine when I place it in my Subject model, but produces an error when I place it in my AdminUser model.
I then tried to comment out basically everything in sight in my AdminUser model to see if I can make the error go away. I finally was able to. The error was apparently caused by another method:
def self.hash(password="")
Digest::SHA1.hexdigest(password)
end
I was supposed to have deleted this method a few video lessons ago when we added these other methods:
def self.make_salt(username="")
Digest::SHA1.hexdigest("Use #{username} with #{Time.now} to make salt")
end
def self.hash_with_salt(password="", salt="")
Digest::SHA1.hexdigest("Put #{salt} on the #{password}")
end
I never deleted the initial one, but for some reason, it was the one causing the error.
So, my question now is: Why did leaving in that method (which was not being used anywhere) cause this "can't convert String into Integer" error?
The reason is that User.hash overrides Object.hash that should return a Fixnum.
You should change it's name for something like User.make_hash
I've looked everywhere for a similar error but couldn't find a solution, so in desperation I'm posting here.
My controller has this:
def add_upc
#upcs = Dvd.add_upc(params[:dogTag], params[:newUpc])
end
and in the Model we have:
def self.add_upc(dogTag, newUpc)
existingUpc = Dvd.find(dogTag).dvd_upc2title.find_by_upc(newUpc)
if existingUpc.nil?
createdUpc = Dvd.find(dogTag).dvd_upc2title.create(:upc => newUpc)
if createdUpc
upcs = createdUpc
else
upcs = 'Error: nothing was created'
end
end
end
I've set up a view page to see what's happening and I can see the object being created by createdUpc. I can also confirm that the parameters dogTag and newUpc are being passed correctly. Yet the record is not being added to the table.
Weirdly, this does work if I issue the Dvd.find(dogTag).dvd_upc2title.create(:upc => newUpc) command with the values substituted for the variables from the IRB.
Can't figure out why this is not working. I'm new to Rails so don't know what other error debugging I could use to figure out where the problem lies.
Ideas are welcome.
Thanks.
Edit:
Found the error thanks to RyanWilcox, it was the validation I had set up in the controller for UPC telling me that value already existed (even though UPCs are supposed to be unique. Is there a way to validate on a combination of 2 fields?
What I really like doing for situations like this ("why did this fail to save?") is using create! instead of create.
This will throw an exception on error, with the failed validation's text as the message of the exception. It makes problems like this obvious.
I have a Rails 3.0.9 app that, once it is deployed, suffers from a bunch of ActiveModel::MissingAttributeErrors that crop up causing 500s. The errors occur fairly randomly, sometimes a page will load, other times it won't, but the attributes are all existing attributes in the database and should be found.
The strange part is that after a while, the errors go away. Suddenly, they stop causing an issue.
I have searched about for a solution to this, but this error mostly occurs either when someone has done Model.all(:select => 'column_x,column_y') and are calling for column_z or when they are using cache_money. I am doing neither of these things.
Can anyone help?
You probably have a query that doesn't return all the columns (i.e. uses :select) and then cache_money; or some other ActiveRecord plugin uses an after_initialize callback, which executes whenever a new ActiveRecord object is created (i.e. when fetched from the database).
In that initialize callback, something tries to access or use an attribute that wasn't included in the :select. You'd expect this to return nil for that attribute, but an ActiveRecord::MissingAttributeError is thrown instead.
You can rescue ActiveRecord::MissingAttributeError like the article suggests, or patch the plugin(s) to use has_attribute?(:attribute_name) before they try to access or modify the attribute.
If you have been having this issue only directly after updating your database without any deploys or server restarts following, then what worked for me may work for you:
Run heroku restart and it should be fixed. Before the dyno restarts old data sometimes remains cached on the server, so starting it up again will scrub all of that data and prevent it from causing errors of that sort. Hope this helps.
I found an interesting take on this that resulted in the same error. In an attempt to reuse code we subclasses a presenters class with a presenters class that performed grouping to use in a graph view.
To simplify, it was something like:
class PostPresenter
def query
Post.where(...stuff....).includes(:wombat)
end
end
The the aggregator did something like the following to build a table of posts per day:
class AggregatePostPresenter < PostPresenter
def group_query
query.select('count(*) as cnt, date(created_at)').group('date(created_at)')
end
end
A call to "group_query" results in an ActiveModel::MissingAttributeError since, I think, the attempt to "includes" Wombat fails because "wombat_id" wasn't in the attributes included in the "select".
This is probably not your answer, however, since it happens regardless of whether or not cache is enabled.
I encountered this issue. Make sure your select: includes all fields referenced in your view, including any relationship IDs and any attributes called within your methods.
The missing attribute can be difficult to identify whenever your views and relationships are complex. The easiest way to debug this is to remove the select portion of your where clause and see if the query/scope/method runs correctly. If so, then add all of the attributes to the select and remove unneeded attributes one-at-a-time until you find the offending attribute.
A similar problem was annoying me when I was trying to make Ajax (actually angularjs) calls to populate an edit-in-place select fields.
I just wanted an id and name attributes to_json and kept getting the MissingAttributeError.
Realised I gotcha'd myself by having an as_json method in the model which is used for the main index and show calls on the model. Basically it was the as_json that was not seeing the attributes it expected.
#foo=Foo.select("id,name")
respond_to do |format|
format.json { render :json => #foo.to_json }
end
gave the error but
respond_to do |format|
format.json { render :json => { :foo=>#foo.as_json(:only=>[:id,:name]) } }
end
seems to be working. I was close to sussing it myself but I found a great explanation at.
http://jonathanjulian.com/2010/04/rails-to_json-or-as_json/
I fixed this by adding .to_json to the end of my controller render.
you need to add line
rescue ActiveRecord::MissingAttributeError
in your after_initialize() method of the model
In my application, these "planners" (essentially, article ideas) follow predetermined templates, written in Markdown, with some specific syntax here:
Please write your answer in the following textbox: [...]
Please write your answer in the following textarea:
...So here, on line, you should write one thing.
...Here, on line 2, you should write another.
...
...
...
Essentially, [...] is a text input, and a group of lines starting with ... are a textarea. That's not really the issue - it's just to explain what part of this code is doing.
On actions new and edit, the standard planner form is displayed, with the correct fields based on the template (for new) or current planner body (for edit). On save, the template's fields are filled in with params[:fields], and the resulting Markdown is saved as the planner's body. The code, I'd hope, is now possible to follow, knowing this context. Only relevant controller code is provided, and it uses make_resourceful.
class Staff::PlannersController < StaffController
make_resourceful do
actions :all
before :create do
find_planner_format
if #planner_format
current_object.body = fields_in_template #planner_format.body
else
flash[:error] = 'Planner format not found!'
redirect_to staff_planners_path
end
current_object.user = #current_user
end
before :update do
current_object.body = fields_in_template(current_object.body)
end
end
private
def fields_in_template(template)
fields = params[:fields] || {}
if fields[:inline]
template.gsub! /\[\.\.\..*\]/ do
"[...#{fields[:inline].shift}]"
end
end
if fields[:block]
template.gsub! /^\.{3}.*(\n\.{3}.*)*$/ do
fields[:block].shift.split("\n").collect { |line|
"...#{line}"
}.join("\n")
end
end
current_object.body = template
end
end
And now, the mystery: in the update action, changes to the body are not saved. After debugging, I've determined that the issue does not lie only in current_object.save, since the following before :update code does what you would expect:
before :update do
current_object.body = 'test string'
end
In fact, even this gets the expected result:
before :update do
current_object.body = fields_in_template(current_object.body) + 'a'
end
So now, the question: why is Rails so insistent that it not save the result of the function - and even then, only when it comes from update? More debugging showed that the object attribute is set, and even claims to save successfully, but reloading the object after save reverts the changes.
At first it looked like the resulting string was just a "poisoned" variable of sorts, and that rebuilding the string by appending "a" removed that strange state. However, the following code, which ought to add an "a" and remove it again, also failed to save.
before :update do
new_body = fields_in_template(current_object.body) + 'a'
new_body.slice! -1
current_object.body = new_body
end
This is just bizarre to me. What am I doing wrong here, and what can I possibly do to debug further? (Or if you happen to instantly see my mistake, that'd be nice, too...)
EDIT: After checking SQL logs (not sure why I didn't think to do this earlier), it would seem that Rails doesn't seem to acknowledge the new body attribute as actually being different, even though checking the string in the debugger confirms that it is. As such, Rails doesn't even run an UPDATE query unless something else is modified, in which case body is not included.
Got it! Sometimes it just helps to state the question out loud...
The deal is, I had forgotten that, when passing current_object.body to fields_in_template, it was being passed by reference. As such, all gsub! methods were running directly on current_object.body, so Rails acknowledged no real "changes" by the time I set body to what had just been set.
The solution:
def fields_in_template(template)
template = template.dup
# ...
end
Thanks for letting me talk to myself, and mission accomplished!
I'm not a Ruby programmer but does adding an 'a' convert the type of the variable to string? Maybe your variable is of the wrong type without adding 'a'.