Audited Gem current_user_method not working - ruby-on-rails

I am using the Audited gem in my application for tracking user log. Everything is working fine except for current user tracking.
In my case, I have 2 models: Instructor and Student. Instructor will be the current_admin_user, and I need to find the student manually.
To overcome this issue, I tried to override current_user_method and create an audited.rb file in the initializers with below content:
Audited.current_user_method = :current_admin_user
This is working fine, but when I use any other method like current_user_or_student ...
Audited.current_user_method = :current_user_or_student
in application_controller.rb ...
def current_user_or_student
current_admin_user || InstructorStudent.find_by_id(id)
end
it is not going into this method, even current_admin_user is also not storing in audits.
Why isn't my current_user_or_student method being called when overriding it in application_controller.rb?

Finally I resolved my issue, I am using STI (single table inheritance) .
my other controllers of instructor was not inherite from application controller. so my current_user_or_student was not called for instructor login .
To overcome this issue, i create one controller concern called audited_user.rb
and write following method in concern
def current_user_or_student
current_admin_user || InstructorStudent.find_by_id(id)
end
and included this concern in both my instructor base controller and application controller.
now everything is working fine and my audited user is also save correctly.
I hope this will save any others day.

You have defined current_user_or_student as such:
def current_user_or_student
current_admin_user || InstructorStudent.find_by_id(id)
end
Assuming current_admin_user is nil, it will try to find InstructorStudent. You are using find_by_id which will return nil if the InstructorStudent with that id cannot be found. It's likely that is what is happening. It's not clear from this source what id is -- are you sure it's set to an id that can be found in instructor_students? I would encourage you to do the following to debug:
def current_user_or_student
current_admin_user || InstructorStudent.find(id)
end
This will raise an error if the method's conditional falls through to the InstructorStudent.find branch and the id cannot be found. If my hypothesis is correct, this will prove that the id cannot be found, so your original code is returning nil (while this updated code now errors instead). The error message will also tell you what the value of id is which you can use for further debugging.
Alternatively, you can debug this without changing code by running a request that you expect to be audited but isn't and then inspecting the server logs. You will see the queries run there and may be able to debug that way as well.

Related

Currently executing code

I'm looking for way to see currently executing code. My reason is: I have ruby classes, that are been monkey patched by lots of patches, so its close to impossible to track which methods were changed. Is there any way to check what code has been loaded into memory and currently executing?
Ask for more details if you need
Another way besides the debugger mentioned by tessi, would be to use ruby-prof to benchmark the whole application and analyze the generated tree.
Every called method is in there, so the desired or not desired versions as well.
Getting the Origin of a Method
In ruby 1.9 and newer, you can open a debugger/console at any point and ask the method for it's source location.
For example, when looking for the definition of the admin? method of my User class I can do the following:
user = User.first
=> #<User id: 1, ...>
user.method(:admin?).source_location
=> ["/Users/tessi/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activemodel-4.0.13/lib/active_model/attribute_methods.rb", 382]
It tells me that the admin? method is defined in ActiveModel in the file above and in line 382 of that file.
In a similar way you could iterate over all methods of your class and check the origin of the methods:
user.methods.map {|method_name| user.method(method_name).source_location}
Monkey Patching
This still works with patched classes. When opening a rails console, I can edit my User class and look at the source_location again:
class User < ActiveRecord::Base
def admin?
puts 'the patched admin? method'
super
end
end
User.first.method(:admin?).source_location
=> ["(pry)",2]
Now, the method's location is in my console (pry) at statement 2. This works, because my patch creates a new Method-object which replaces the old method in the method dictionary of the User class. The new method object returns a different source_location.

Cannot cache from decorator (draper)

Caching is by far the most logic-intensive part of my view code, so I would like to do fragment caching from inside a decorator, however, I cant do it.
When i do this from my decorator:
def cached_name
h.cache do
"a name here"
end
end
I get this:
You have a nil object when you didn't expect it! You might have
expected an instance of Array. The error occurred while evaluating
nil.length
I instantiate my decorator from inside a controller
#presenter = SomePresenter::new
I am using HAML for my views
How can I succesfully cache from inside my decorator, so my view can do stuff like this
= #decorator.cached_logic_heavy_stuff
UPDATE: I have created a git repo showing my issue: https://github.com/houen/presenter_caching
UPDATE: This maybe works - see the repo
include Haml::Helpers
def another_way_to_try
self.init_haml_helpers
buffer = haml_buffer.buffer
h.with_output_buffer(buffer) do
h.cache do
h.concat "i should still not be empty"
end
end
end
I'd suggest using Rails.cache directly might solve your problem; we do the same thing in our decorators with Rails 4.
def cached_name
Rails.cache.fetch(source) do
source.name # etc.
end
end
If you're using Draper, I believe you don't need to explicitly pass the view context. You will likely want to pass a model or collection to your draper present when you instantiate. Examples:
class UserDecorator < Draper::Base
decorates :user
# additional methods
end
# in the controller
#presenter = UserDecorator.new(#user) # for an instance
#presenter = UserDecorator.decorate(#users) # for a collection
I suspect the nil object error you're getting is coming from another method call that's not listed in your code.
As for fragment caching from your decorator, you'll want to use the concat helper method to get this to work inside the decorator:
# your decorator class
def cached_name
h.cache("some_cache_key") do
h.concat "a name here"
end
end
Rails' cache method tries to infer a cache key based on the view that it's being called from. Since you're not actually calling it from a view (but from inside an instance of a decorator class), I expect that it's bombing when trying to build a cache key.
You might try passing a cache key explicitly, via h.cache "your cache key" do. With a full stack trace, you can figure out where it's throwing the exception, and then work around that, as well. Without the full stack trace, it's harder to help you, though.
Edit: Looking at Rails' caching code, I think this might be a deeper issue; it's attempting to get the length of output_buffer, which isn't going to be available outside of your views' contexts (that is, within Draper). You might try adding:
def output_buffer
h.output_buffer
end
But without testing it, I'm thinking it might not work exactly as planned without some more work. This is just a rough guess - I'd be surprised if this is actually the issue, but hopefully it gets you on the right path.
The note in the source there:
# VIEW TODO: Make #capture usable outside of ERB
# This dance is needed because Builder can't use capture
indicates that this isn't a fully-solved problem, so you may need to do a little digging around in the Rails internals to make this one work.
This works:
include Haml::Helpers
def another_way_to_try
self.init_haml_helpers
buffer = haml_buffer.buffer
h.with_output_buffer(buffer) do
h.cache "some_key10", :expires_in => 10.seconds do
h.concat "i should still not be empty 2"
end
end
end

Why does this unused self.hash method cause a "can't convert String into Integer" error?

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

Rails Devise: How do I (mem)cache devise's database requests for the user object?

Every time I hit an authenticated page, I notice devise issuing an SQL statement :
User Load (0.2ms) SELECT users.* FROM users WHERE (users.id = 1) LIMIT 1
(I'm using Rails 3 btw .. so cache_money seems out as a solution and despite a lot of searching I've found no substitute).
I tried many overrides in the user model and only find_by_sql seems called. Which gets passed a string of the entire SQL statement. Something intuitive like find_by_id or find doesn't seem to get called. I 'can' override this method and glean the user-id and do a reasonable cache system from that - but that's quite ugly.
I also tried overriding authenticate_user which I can intercept one SQL attempt but then calls to current_user seems to try it again.
Simply, my user objects change rarely and its a sad state to keep hitting the db for this instead of a memcache solution. (assume that I'm willing to accept all responsibility for invalidating said cache with :after_save as part but not all of that solution)
The following code will cache the user by its id and
invalidate the cache after each modification.
class User < ActiveRecord::Base
after_save :invalidate_cache
def self.serialize_from_session(key, salt)
single_key = key.is_a?(Array) ? key.first : key
user = Rails.cache.fetch("user:#{single_key}") do
User.where(:id => single_key).entries.first
end
# validate user against stored salt in the session
return user if user && user.authenticatable_salt == salt
# fallback to devise default method if user is blank or invalid
super
end
private
def invalidate_cache
Rails.cache.delete("user:#{id}")
end
end
WARNING: There's most likely a better/smarter way to do this.
I chased this problem down a few months back. I found -- or at least, I think I found -- where Devise loads the user object here:
https://github.com/plataformatec/devise/blob/master/lib/devise/rails/warden_compat.rb#L31
I created a monkey patch for that deserialized method in /initializers/warden.rb to do a cache fetch instead of get. It felt dirty and wrong, but it worked.
I've been struggling with this, too.
A less convoluted way of doing this is to add this class method to your User model:
def self.serialize_from_session(key, salt)
single_key = key.is_a?(Array) ? key.first : key
Rails.cache.fetch("user:#{single_key}") { User.find(single_key) }
end
Note that I'm prepending the model name to the object ID that is passed in for storing/retrieving the object from the cache; you can use whatever scheme fits your needs.
The only thing to worry about, of course, is invalidating the user in the cache when something changes. It would have been nice instead to store the User in the cache using the session ID as part of the key, but the session is not available in the model class, and is not passed in to this method by Devise.

Rails updating attribute security: use a callback or attr_accessible?

I've got a website model that requires a user to verify ownership of the website.
Thanks to stack overflow, I was able to find the solution for user ownership verification here: Validate website ownership in rails
After the model passes the verification test there is a verified attribute that gets set to true.
The problem I'm running into is when the user wants to edit attributes of his or her website, he or she could easily change the name of the domain while the verified attribute remains true, thus allowing the user to create website objects without verifying ownership.
I can think of two ways to solve this:
1. Have a callback that changes the verification to false if the domain name of the website gets changed.
2. Allow attr_accessible for the domain upon the creation of a new object but not when updating it.
I'm stumped as to how to implement either of these practically.
Callbacks and Active Record Dirty methods are definitely the way to go for this type of situation.
before_update :set_domain_status
def set_domain_status
self.verified = false if self.domain_changed?
end
_changed? can be added to any attribute, returning true if the value has changed from that originally loaded from the database.
I think your Option#1 is the best route. Otherwise you get into the business of trying to reconcile create and update actions - which you would need to do to handle Option #2.
You could override the setter for domain name and then perform custom logic, like so:
In your Model:
def domain=(the_domain)
raise ValidOwnerRequired if verified? && domain != the_domain
# if we got here then the this record is new and this is a creation attempt
#require_domain_verification = true
# do other stuff here..
end
def require_domain_verification?
#require_domain_verification == true
end
And then have an observer for that model:
def after_save(record)
if record.require_domain_verification?
SomeMailer.deliver_domain_verification(record)
end
end
Something like that...
Cody, your answer got me on the right track. Thanks a lot!
This is what I went for in my case:
def domain=(the_domain)
if domain != the_domain
self[:domain] = the_domain
self[:verified] = false
end
end
It works just fine.

Resources