Forum: Ruby on Rails run :before_filter before loading controller's ActiveRecord model?

Fcfb86addd7d70c57f113fedfcb9fb6a?d=identicon&s=25 Tom Shealy (tom_302)
on 2012-07-28 05:21
(Received via mailing list)
Hello,


I'm creating a Rails app on top of a legacy document management
system.  I'm looking for a way to force an ApplicationController's
:before_filter method to execute before the ActiveRecord model is
evaluated:


class *Document* << ActiveRecord::Base

  acts_as_controlled

end


My acts_as_controlled() method generates different SQL criteria &
bindings
for scopes and finders based on the state of the Document and whether
it's
checkout out by the current user (MGR.user) vs checked out by another
user,
vs checked in.  The model's default_scope matches the check in, checked
out, or working copy accordingly to provide the correct records.


The legacy application uses basic http auth and my rails application is
deployed in the same context, so I obtain MGR.user from the http request
using :before_filter:


class *ApplicationController* < ActionController::Base

  before_filter :authenticate

  def authenticate

        auth_data = Base64.decode64(request.authorization.split(' ',
2).last || '').split(/:/, 2)

        MGR.user=auth_data[0]

        MGR.pass=auth_data[1]

   end

end


class *DocumentsController* < ApplicationController

  def index

    @documents = Document.all

    respond_to do |format|

      format.html # index.html.erb

      format.json { render :json => @documents }

    end

  end

end



This authentication works only if the first request goes to a controller
that doesn't use an ActiveRecord model that  acts_as_controlled.  If the
initial request goes to DocumentController, the Document model is loaded
and acts_as_controlled is called *before* ApplicationController's
:before_filter can set MGR.user:


Started GET "/MyApp/documents" for 127.0.0.1 at Thu Jul 26 18:05:52
-0700
2012


NativeException ([from a java method of the legacy application]):

  config/initializers/myapp.rb:169:in `current_user'

  config/initializers/myapp.rb:351:in `define_model_scope'

  config/initializers/myapp.rb:625:in `acts_as_controlled'

  app/models/document.rb:2:in `Document'

  app/models/document.rb:1:in `(root)'

  app/models/document.rb:456:in `load_file'

  app/controllers/documents_controller.rb:1:in `(root)'

*  app/controllers/documents_controller.rb:456:in `load_file'*


Rendered
vendor/bundle/jruby/1.8/bundler/gems/rails-80f6547f5b25/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
(27.0ms)

Rendered
vendor/bundle/jruby/1.8/bundler/gems/rails-80f6547f5b25/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
(3.0ms)

Rendered
vendor/bundle/jruby/1.8/bundler/gems/rails-80f6547f5b25/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
within rescues/layout (46.0ms)


One solution would be to short-circuit acts_as_controlled if MGR.user
isn't
set, store a reference to the model, and finally execute
acts_as_controlled
on all referenced models at the end of the :before_filter method, but
that
approach would mean evaluating each model twice.


Is there a better way to make ApplicationController :before_filter
execute
before the Document model is evaluated by DocumentController?


PS:  Also, is it even safe to store the user id in a constant like MGR?
I
haven't seen any warnings about it being redefined so far, but I'm not
quite sure how rails instances are managed across requests & sessions
with
JRuby and Tomcat.


Thanks in advance for your advice.
81b61875e41eaa58887543635d556fca?d=identicon&s=25 Frederick Cheung (Guest)
on 2012-07-28 17:07
(Received via mailing list)
On Saturday, July 28, 2012 4:20:36 AM UTC+1, tom_302 wrote:

>
>
vendor/bundle/jruby/1.8/bundler/gems/rails-80f6547f5b25/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
>
>
>
This sounds horribly brittle (and in production mode the whole
application
is loaded at boot time, so I think you'll have problems too). I think
you'd
be better off rethinking how your acts_as_controlled method works

(for example generate the scopes using lambda so that they can change
their
conditions at runtime)



> PS:  Also, is it even safe to store the user id in a constant like MGR?  I
> haven't seen any warnings about it being redefined so far, but I'm not
> quite sure how rails instances are managed across requests & sessions with
> JRuby and Tomcat.
>

That depends entirely on what MGR.user= does. That could be implemented
in
a threadsafe way (eg using Thread.current) or in a thread dangerous way

Fred
Fcfb86addd7d70c57f113fedfcb9fb6a?d=identicon&s=25 Tom Shealy (tom_302)
on 2012-07-29 19:55
(Received via mailing list)
Thanks - and good call on the lambda for delaying evaluation of the
user_id.

Unfortunately, I also need the user_id to authenticate with the legacy
application in order to load its codebase; I'm defining my ActiveRecord
models dynamically from this codebase.

I've been able to use AR scopes to hide all of the
checkin/checkout/version
control.  And I can pull the http authentication off any request.  I
just
wish the controller could evaluate my :before_filter before it evaluates
its AR models.

I'm looking into alternatives, but I'd have to give up the dynamic model
definition and loose some flexibility there.

Regarding storing the user_id in a constant, i thought rails doesn't
share
any information between requests; Isn't it up to the server to keep
variable states separate however it loads/shares instances of the rails
application?  Please explain.
81b61875e41eaa58887543635d556fca?d=identicon&s=25 Frederick Cheung (Guest)
on 2012-07-29 20:27
(Received via mailing list)
On Sunday, July 29, 2012 6:54:50 PM UTC+1, tom_302 wrote:
>
I really think that anything that depends on class load order is fatally
flawed (and as I said before, in production mode rails will load your
app classes before the first request arrives.

>
> Regarding storing the user_id in a constant, i thought rails doesn't share any
information between requests; Isn't it up to the server to keep variable states
separate however it loads/shares instances of the rails application? Please
explain.
>
>
Rails doesn't enforce anything like this. Controller instances only last
the length of the corresponding request, so anything set there
'disappears' but (except in development mode) classes aren't reloaded
between requests : class variables, constants etc. are shared across
requests, and will obviously trigger weird behaviour if you run rails in
threadsafe modd

Fred
Fcfb86addd7d70c57f113fedfcb9fb6a?d=identicon&s=25 Tom Shealy (tom_302)
on 2012-07-29 21:11
(Received via mailing list)
What's the best place to keep the user_id from the request, so it can be
referenced from the scope's lambda method?  Rather than store it in
MGR.user_id, is there a safer place to store it?  (or is there a way to
access the request object outside of the controller?)
81b61875e41eaa58887543635d556fca?d=identicon&s=25 Frederick Cheung (Guest)
on 2012-07-29 22:52
(Received via mailing list)
On Sunday, July 29, 2012 8:10:11 PM UTC+1, tom_302 wrote:
>
> What's the best place to keep the user_id from the request, so it can be
> referenced from the scope's lambda method?  Rather than store it in
> MGR.user_id, is there a safer place to store it?  (or is there a way to
> access the request object outside of the controller?)
>
> MGR.user_id isn't necessarily a bad thing - it just depends how that is
implemented:

#Unsafe
class MGR
  class << self
    attr_accessor :user_id
  end
end

#Safe
class MGR
  class << self
    def user_id
      Thread.current[:mgr_user_id] = value
    end

    def user_id
      Thread.current[:mgr_user_id]
    end
  end
end

If you're concerned about polluting the Thread.current namespace you can
also use a hash keyed by the id of the current thread although that does
require that you cleanup old values from the hash

Fred
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.