Rails 3 sessions not lazy loading - ruby-on-rails

I've read that sessions in Rails 3 are lazy loaded, but I'm not seeing that behavior. To test this, I created a new Rails 3.2 app using MySQL and the activerecord session store. No gems added except the V8 JavaScript engine. Then I created an empty controller called welcome with an index action. No code in here at all. When I hit the index page, I see a session created in the sessions table in MySQL.
Am I doing something wrong? I thought a session would only be created if I accessed it.

That's a default behavior of Ruby on Rails beginning from version 3.0 I guess. See:
http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf

If you need to stop sessions being created in the database for bots/crawlers, this is what worked for me:
# Save this file as config/initializers/session_store_ext.rb
# and don't forget to define BOT_REGEX to match bots/crawlers
class ActiveRecord::SessionStore
_set_session = instance_method :set_session
define_method :set_session do | env, sid, session_data, options |
unless env['HTTP_USER_AGENT'] =~ BOT_REGEX
_set_session.bind(self).call env, sid, session_data, options
end
sid
end
private :set_session
end
I've written a blog post explaining how I created this and why it works - Conditionally Disabling Database Sessions in Ruby on Rails 3

Related

Reading Rails 4.1 session cookie in Rails 6

I’m attempting to strangle a legacy Rails 4.1 application by using a proxy in front of it and sending some requests to a new Rails 6 application.
The Rails 4 application is responsible for user authentication and setting the initial session cookie.
Is there a way to get the Rails 6 application to read/write Rails 4.1 compatible session cookies?
Thanks
I wanted to write an answer here as I recently had to dig through some Rails guts to solve this issue. First, make sure both apps share the same secret_key_base, cookie domain, and cookie key. This will let the apps at least read the same session cookie:
MyApp::Application.config.session_store :cookie_store, {
domain: 'myapp.com', # can omit if they're on the same domain
key: '_myapp_session', # need to be explicit here
expire_after: 60.minutes
}
Then, you need to make sure both apps can read the actual payload. The default cookie serialization is moving to JSON in Rails 7, but any older version will still default to :marshal (which uses the basic Ruby Marshal module). Make sure you're using the same serializer.
Rails.application.config.action_dispatch.cookies_serializer = :marshal
Lastly, if your newer app is Rails 6.2+, be warned that Rails started wrapping all cookie payloads (regardless of serialization method) in a JSON envelope. This envelope can't be opened by Rails 4.2 or older apps. So your session can move from Rails 4.2 -> 6.2, but not back...
Unless you monkeypatch it with a custom deserializer, like so:
class WrappedSerializer
def self.base_serializer
Marshal # or JSON, if you're using :json above
end
def self.load(value)
# if this is a Rails 4-created session, parsing will fail & hit the rescue
json = JSON.parse(value) rescue nil
return base_serializer.load(value) if json.nil? || !json.key?('_rails')
base_serializer.load(Base64.decode64(json.dig('_rails', 'message')))
end
def self.dump(value)
base_serializer.dump(value)
end
end
module ActionDispatch
class Cookies
module SerializedCookieJars
def serializer
WrappedSerializer
end
end
end
end
I should point out that, because this strips the envelope which might contain a custom expiry time, the Rails 5.2+ feature of being able to set custom expiry times will not work for any sessions that transition back to your Rails 4 app. If you use that feature, you could probably find a way change the above serializer to rewrap the payload when it deserializes.

Dynamically override destination file of all Logger output in Rails

I am using the Apartment gem for a multi tenant Rails 5.2 app. I'm not sure that this even matters for my question but just giving some context.
Is there a way to override the Rails logger and redirect every single log entry to a file based on the tenant (database) that is being used?
Thinking... is there a method I can monkeypatch in Logger that will change the file written to dynamically?
Example: I want every error message directed to a file for that day. So at the end of a week there will be 7 dynamically generated files for errors that occurred on each specific day.
Another example: Before you write any server log message check if it is before 1pm. If it is before 1pm write it to /log/before_1.log ... if it is after 1pm write it to /log/after_1.log
Silly examples... but I want that kind of dynamic control before any line of log is written.
Thank you!
Usually the logger is usually configured per server (or per environment really) while apartment sets tenants per request - which means that in practice its not really going to work that well.
You can set the logger at any point by assigning Rails.logger to a logger instance.
Rails.logger = Logger.new(Rails.root.join('log/foo.log'), File::APPEND)
# or for multiple loggers
Rails.logger.extend(Logger.new(Rails.root.join('log/foo.log'), File::APPEND))
However its not that simple - you can't just throw that into ApplicationController and think that everything is hunky-dory - it will be called way to late and most of the entries with important stuff like the request or any errors that pop up before the controller will end up in the default log.
What you could do is write a custom piece of middleware that switches out the log:
# app/middleware/tenant_logger.rb
class TenantLogger
def initialize app
#app = app
end
def call(env)
file_name = "#{Appartment::Tenant.current}.log"
Rails.logger = Logger.new(Rails.root.join('log', file_name), File::APPEND)
#app.call(env)
end
end
And mount it after the "elevator" in the middleware stack:
Rails.application.config.middleware.insert_after Apartment::Elevators::Subdomain, TenantLogger
However as this is pretty far down in the middleware stack you will still miss quite a lot of important info logged by the middleware such as Rails::Rack::Logger.
Using the tagged logger as suggested by the Rails guides with a single file is a much better solution.

The correct way of initialising Google api client on rails

I have started with this example, I managed to make everything work on sinatra.
But I want to use this in my Rails app so I did the followings:
Create the client, in sinatra they do that in the configure block.
In rails I did:
In the config/initializers file I've added the entire block which is in sinatra after that I wrote a class GoogleCalendar::Base where I have:
class GoogleCalendar::Base
class << self
attr_accessor :api_client, :calendar_api
end
def self.configure
yield self if block_given?
end
So like this I replaced the set :api_client, client, set :calendar, calendar
In my application controller then I've added the:
def user_credentials
...
(what i've changed was instead of session i've added `see below`)
auth.update_token!(access_token: session[:access_token],
refresh_token: session[:refresh_token])
end
Because I wasn't working if passing session maybe someone can tell me why? (conflict between sessions? and it was working with cookies.)
Also I have created other functions in the application controller which I use for callback and get content.
def api_client
GoogleCalendar::Base.api_client
end
def calendar
GoogleCalendar::Base.calendar_api
end
In the end I've created the rest of the controllers/views and everything works fine.
My final question is: It is ok creating the Google::APIClient in the initialisers folder because I've notice that every time I change something in my controllers the code brakes, but if I restart the server everything works ok.

Rails finding by params

I'm building a API and want the show page for a user to be found by 'uid' instead of the record ID
I have this in my controller
def show
respond_with User.find_by_uid(params[:uid])
end
When I go to localhost/api/v1/users/8888888 Its returns "Null"
Finding by ID seems to work fine, am I doing something wrong here?
I tried put this in the rails console and it worked
User.find_by_uid("8888888")
I'm new to rails
Thanks
have you tried visiting:
localhost/api/v1/users?uid= 8888888 instead of the url you are using currently, except you are handling that correctly rails would have no knowledge of the uid param alternatively you could add this to your config/routes.rb file
get 'users/:uid', to: 'users#show'
With the hope that your controller is called UsersController then you can call localhost/api/v1/users/8888888 in your browser and it should behave as expected
Rather than just giving you the answer, I'll provide a tip on debugging Ruby applications (including Rails).
Get the pry gem and the pry-debugger gem and include them in your Rails application (there's plenty of posts around Google on how to include pry and pry-debugger in Rails).
put 'binding.pry' (without the quotes) at the beginning of your show method. In the console where your server runs, when show gets executed, execution will halt/pause at binding.pry. Type the following in the pry console to see what is available in the rails params hash.
pry> params
(this will print out the contents of params)
I would start my troubleshooting here, and post the contents of params and any relevant server logging here if you still can't figure it out.
edit
I don't have enough rep to comment, yet. Only really been on this site and using it a day or two.

Losing namespace information in a rails namespaced model

When you create a namespaced model with rails scaffolding, you get two files. For example, this scaffold:
rails generate model Staff::Location name:string address:string
Generates these files:
/app/models/staff.rb
module Staff
def self.table_name_prefix
"staff_"
end
...
/app/models/staff/location.rb
class Staff::Location < ActiveRecord::Base
...
I am running into problems when in development mode where rails unloads the Staff module and never reloads it. This causes several annoying bugs such as Location not able to access it's table due to the missing table_name_prefix. The problem seems to crop up when I don't access the models directly, such as through a polymorphic relationship.
I can't seem to get the module loaded on a consistent basis. Is this the best practice way to do namespaced models? If it is, what am I missing?
Although I wasn't able to reproduce the problem in Rails 3.2.2, I've run into something like this before. The generic way to hack around this problem in development mode is through an ActionDispatch callback. Add this to config/environments/development.rb:
MyApp::Application.configure do
ActionDispatch::Callbacks.before do
load Rails.root.join('app', 'models', 'staff.rb')
end
end
Anything you do in that block will be executed before each request, so make sure you're only doing it in development mode.† Otherwise, you're going to suffer a performance hit in production.
I logged a message inside the staff.rb file and within the Staff module itself, and both messages appeared in the log for each request.
† I tried using the to_prepare callback, since that seems to be the documented way to execute code before each request only when cache_classes is false. But that only seemed to execute after restarting the application. There's at least one other open Stack Overflow question regarding this, although he's using a slightly different syntax than I used. If you can get to_prepare to work, I'd suggest that instead of before.
About a year later, I have finally found the answer to this question. This answer is specifically for rails 3.1. I am not sure if it is a problem in rails 3.2.
The problem occurs when setting up a model. If scaffolding is used, no helper file is generated. This would normally be in /app/helpers/staff/location_helper.rb. There are two ways to setup this file:
module Staff::LocationHelper
...
end
module Staff
module LocationHelper
...
end
end
In rails 3.1, specifically for helpers, you must use the first solution. You do not have to use it for other modules that use a namespace in other parts of the rails project. In fact, some structures in ruby require the second solution.
If you use the second solution when declaring a helper, in certain cases the Staff module in the helper file will override the module in /app/models/staff.rb. It will silently replace it with the empty Staff module in the file. This does not happen 100% of the time because helpers are not always loaded.

Resources