Thread safety of a class variable in Rails - will this work? - ruby-on-rails

I'm using the Ruby Money gem in a multi-tenant (SaaS) Rails app, and am looking for a good way to make the Money.default_currency be set to an Account's preference for each request. I have several currency-related models in the app that use the Money class.
I have everything working properly in development, but I'm just looking for some feedback on whether or not the solution with have repercussions in production.
Here's what I did in my ApplicationController (irrelevant code removed for brevity):
class ApplicationController < ActionController::Base
before_filter :set_currency
private
def set_currency
Money.default_currency = Money::Currency.new(current_account.present? && current_account.currency.present? ?
current_account.currency : 'USD')
end
end
So the code above will set the default_currency class variable to the current account's preference, or default back to 'USD' if there isn't one.
By the way, here's the relevant default_currency code in the Money class:
class Money
# Class Methods
class << self
# The default currency, which is used when +Money.new+ is called without an
# explicit currency argument. The default value is Currency.new("USD"). The
# value must be a valid +Money::Currency+ instance.
#
# #return [Money::Currency]
attr_accessor :default_currency
end
end
So, will this work as expected in a multi-user setting? Anything else I need to do?

Most rails apps don't run in multithreaded mode - a given instance is only ever handling one request at a time (this is the default).
If your app was in multithreaded mode this would be dangerous - Money.default_currency could get changed halfway through a request by the new request that has just come in. If you did want to make this thread safe, you could use the Thread.current hash to have per thread values of default_currency

Related

Set per request i18n fallbacks in Rails?

I have a multi-country Rails app. And I need to set per request i18n fallbacks schema. Is it possible and how to achieve this?
UPD
class Country < ApplicationRecord
# String attribute "languages" (array)
end
class CountriesController < ApplicationController
def show
#country = Country.find params[:id]
I18n.fallbacks = {
#country.languages.first => #country.languages.second
} # This does not work
render 'show'
end
end
Experimenting a bit (with Rails 6), it is possible to change the fallbacks using the Simple (default) backend, but doing so is not thread-safe and will likely cause problems if you do it on a per-request basis. It's somewhat counter-intuitive -- setting I18n.locale is the documented way to dynamically set locale per request, so it's natural to assume fallbacks would work the same way. However, from the the i18n source:
The only configuration value that is not global and scoped to thread
is :locale.
Even that isn't very clearly worded. But indeed locale is defined as an instance variable, and all other configuration attributes are ## class variables.
The Rails guide for I18n says that the Simple (default) backend was designed to only do the "simplest thing that could possibly work", but the framework allows for plugging in custom backends that go beyond that. So, the best way for you to achieve your result will be to find (or create) a backend that supports per-request fallbacks in a thread-safe way.
For reference, if someone does need to change a language fallback outside the initializer (again, that's globally), fallbacks.map() does that:
I18n.fallbacks.map(:ca => :"es-ES")
My original answer mentioned fiddling with the fallback hash directly, but using .map() preserves falling back to the default locale.
Adding to what rmlockerd said, due to fallbacks being a class variable, I recommend you restore your fallbacks after every request just to ensure that you're not accidentally leaking it to other requests in the same server instance. You could do it like so:
around_action :restore_i18n_fallbacks
# ...
private
def restore_i18n_fallbacks(&action)
original_fallbacks = I18n.fallbacks
begin
action.call
ensure
I18n.fallbacks = original_fallbacks
end
end
(Code not tested but should work or be fairly close to working.)

Rails 3.2 how to protect shared resource access across sessions

I have a shared resource that can only be used by one session at a time, how do I signal to other sessions that the resource is currently in use?
In Java or C I would use a mutex semaphore to coordinate between threads, how can I accomplish that in Rails? Do I define a new environment variable and use it to coordinate between sessions?
A little code snippet along with the answer would be very helpful.
Since your Rails instances can be run in different processes when using Nginx or Apache (no shared memory like in threads), I guess the only solution is using file locks:
lock = File.new("/lock/file")
begin
lock.flock(File::LOCK_EX)
# do your logic here, or share information in your lock file
ensure
lock.flock(File::LOCK_UN)
end
I would consider using Redis for locking the resource.
https://redis.io/topics/distlock
https://github.com/antirez/redlock-rb
This has the advantage of working across multiple servers and not limiting the lock time to live to the lifetime of the current HTTP request.
Ruby has a Mutex class that might do what you want, though it won't work across processes. I apologize though that I don't know enough to give you an example code snippet. Here's what the documentation says: "Mutex implements a simple semaphore that can be used to coordinate access to shared data from multiple concurrent threads."
You can do this with acts_as_lockable_by gem.
Imagine the shared resource is a Patient ActiveRecord class that can only be accessed by a single user (you can replace this with session_id) as follows:
class Patient < ApplicationRecord
acts_as_lockable_by :id, ttl: 30.seconds
end
Then you can do this in your controller:
class PatientsController < ApplicationController
def edit
if patient.lock(current_user.id)
# It will be locked for 30 seconds for the current user
# You will need to renew the lock by calling /patients/:id/renew_lock
else
# Could not lock the patient record which means it is already locked by another user
end
end
def renew_lock
if patient.renew_lock(current_user.id)
# lock renewed return 200
else
# could not renew the lock, it might be already released
end
end
private
def patient
#patient ||= Patient.find(params[:id])
end
end
This is a solution that works with minimum code and across a cluster of RoR machines/servers not just locally on one server (like using file locks) as the gem uses redis as locks/semaphores broker. The lock, unlock and renew_lock methods are all atomic and thread-safe ;)

Permanent variable in Rails

Lets say that on top of my Rails app there is a bar with piece of text displayed - latest hot deal, scheduled downtime notfication, something like that. It's a single, on of a kind information that needs to be accessed on basically every request, and may be updated from time to time. What is the best way to achieve this?
What I'd like to do is some kind of permanent global variable (accessible from controllers).
It will be updated very rarely, so there's no problem if for some time after update there will be an inconsistency between workers.
On the other hand, it should be persistent in case of server fault (periodic backup is enough).
It will be accessed really often, so it should be as fast as possible - preferably stay in memory.
Also, it's only one of a kind, so I'd really prefer not to bloat the app with a dedicated database model.
Something like that is damn easy in Node.js for example, but I couldn't find a single way to achieve this in Rails. What shall I do?
EDIT
Thanks for the answers so far, but while they're inspiring, I think that I should stress out one key functionality that they're all missing. The variable should be editable inside the app and persistent. While it's possible to edit your variables, in case of server restart I'm back to the default - which is bad.
It really depends on what you are looking for. You could do something very simply by putting it in your application_controller.rb
class ApplicationController < ActionController::Base
def system_message
"Come buy our amazing .99 iphone chocolate bar apps, with 100% more gamification!"
end
end
That function (and string) is then accessible from any controller in your application. You could also specify something in the after_initialize block in your application.rb file.
config.after_initialize do
::MYTEXT = "MY SUPER AMAZING TEXT"
end
You could also create your own file under the initializers directory, which is preloaded in rails.
so siteAnnounce.rb
MYANNOUNCEMENT = "NOW LISTEN TO ME!"
You may also want to check out this Railscast video about site wide announcements
I would store it in the database and let caching take care of it.
I feel that global variables are fine, when appropriate, for code that needs to share that common value in many places but that is the code, not the the user view.
This is clearly true in this case as the OP has bolded 'editable by the app'. So I would have a view that lets the users enter it, it gets stored in a db table and then recalled as needed (as cached once used once).
Well I had faced a similar problem.
My problem was I needed a global variable in all the levels (MVC).
We went to use Memcache to store the variable.
May be you can go for a similar solution.
And as an added bonus you can change it throughout the program.
You could declare it as a constant in an initializer:
config/initialzers/foo.rb:
MYVARIABLE = 'some string'
Accessible from anywhere in your application as MYVARIABLE
Ok, so here's what I did. Instead of just putting the value to an initializer, I've made there a simple class that handles it. The variable itself is stored in a predefined file. Besides of reading the file upon the initialization, the class updates file when the value is changed, and also re-read the file periodically to maintain consistency across workers. I've also put there some basic JSON handling and backup functionality to make life easier.
For anyone interested, here's the important code:
class Pomegranate
def initialize
#delay = 30.minutes
#path = "db/pomegranate.json"
#valid = Time.now - 1
validate
end
def get(*p)
validate
p.inject(#data) {|object,key| object[key] if object}
end
def set(*p, q, v)
hash = p.inject(#data) {|object,key| object[key]||={}}
hash[q] = v
end
def save
#valid = Time.now + #delay
File.open(#path,"w") {|f| f.write(#data.to_json)}
end
private
def validate
if #valid < Time.now
#data = ActiveSupport::JSON.decode(File.read(#path)) rescue {}
#valid = Time.now + #delay
#valid = Time.now - 1 if #data.empty?
end
end
end
$pom = Pomegranate.new
Source:
Where to put Global variables in Rails 3
Try putting it in your applicaton.rb like this:
module MyAppName
class Application < Rails::Application
YOUR_GLOBAL_VAR = "test"
end
end
Then you can call it with the namespace in your controllers, views or whatever..
MyAppName::Application::YOUR_GLOBAL_VAR
Another alternative would be using something like settingslogic. With settingslogic, you just create a yml config file and a model (Settings.rb) that points to the config file. Then you can access these settings anywhere in your rails app with:
Settings.my_setting
I've started putting constants and variables like this in the configuration object, e.g.
TestApp::Application.config.foo = 'bar'
TestApp::Application.config.something = { :a => 1, :b => 2 }

What is the best way to obfuscate numerical IDs in an application

Given I've got a site where most of the resources have numerical IDs (i.e. user.id question.id etc.) but that like the Germans looking back on WWII I'd rather not reveal these to the observers, what's the best way to obfuscate them?
I presume the method is going to involve the .to_param and then some symmetric encryption algorithm but I'm not sure what's the most efficient encryption to do and how it'll impact lookup times in the DB etc.
Any advice from the road trodden would be much appreciated.
I published a Rails plugin that does this called obfuscate_id. I didn't need it to be secure, but just to make the id in the url non-obvious to the casual user. I also wanted it to look cleaner than a long hash.
It also has the advantage of needing no migrations or database changes. It's pretty simple.
Just add the gem to your Gemfile:
gem 'obfuscate_id'
And add call the obfuscate id in your model:
class Post < ActiveRecord::Base
obfuscate_id
end
This will create urls like this:
# post 7000
http://example.com/posts/5270192353
# post 7001
http://example.com/posts/7107163820
# post 7002
http://example.com/posts/3296163828
You also don't need to look up the records in any special way, ActiveRecord find just works.
Post.find(params[:id])
More information here:
https://github.com/namick/obfuscate_id
I usually use a salted Hash and store it in the DB in an indexed field. It depends on the level of security you expect, but I use one salt for all.
This method makes the creation a bit more expensive, because you are going to have an INSERT and an UPDATE, but your lookups will be quite fast.
Pseudo code:
class MyModel << ActiveRecord::Base
MY_SALT = 'some secret string'
after_create :generate_hashed_id
def to_param
self.hashed_id
end
def generate_hashed_id
self.update_attributes(:hashed_id => Digest::SHA1.hexdigest("--#{MY_SALT}--#{self.id}--"))
end
end
Now you can look up the record with MyModel.find_by_hashed_id(params[:id]) without any performance repercussions.
Here's a solution. It's the same concept as Wukerplank's answer, but there's a couple of important differences.
1) There's no need to insert the record then update it. Just set the uuid before inserting by using the before_create callback. Also note the set_uuid callback is private.
2) There's a handy library called SecureRandom. Use it! I like to use uuid's, but SecureRandom can generate other types of random numbers as well.
3) To find the record use User.find_by_uuid!(params[:id]). Notice the "!". That will raise an error if the record is not found just like User.find(params[:id]) would.
class User
before_create :set_uuid
def to_param
uuid
end
private
def set_uuid
self.uuid = SecureRandom.uuid
end
end
Hashids is a great cross-platform option.
You can try using this gem,
https://github.com/wbasmayor/masked_id
it obfuscates your id and at the same time giving each model it's own obfuscated code so all no. 1 id won't have the same hash. Also, it does not override anything on the rails side, it just provides new method so it doesn't mess up your rails if your also extending them.
Faced with a similar problem, I created a gem to handle the obfuscation of Model ids using Blowfish. This allows the creation of nice 11 character obfuscated ids on the fly. The caveat is, the id must be within 99,999,999, e.g. a max length of 8.
https://github.com/mguymon/obfuscate
To use with Rails, create an initializer in config/initializers with:
require 'obfuscate/obfuscatable'
Obfuscate.setup do |config|
config.salt = "A weak salt ..."
end
Now add to models that you want to be Obfuscatable:
class Message < ActiveRecord::Base
obfuscatable # a hash of config overrides can be passed.
end
To get the 11 character obfuscated_id, which uses the Blowfish single block encryption:
message = Message.find(1)
obfuscated = message.obfuscated_id # "NuwhZTtHnko"
clarified = message.clarify_id( obfuscated ) # "1"
Message.find_by_obfuscated_id( obfuscated )
Or obfuscate a block of text using Blowfish string encryption, allowing longer blocks of text to be obfuscated:
obfuscated = message.obfuscate( "if you use your imagination, this is a long block of text" ) # "GoxjVCCuBQgaLvttm7mXNEN9U6A_xxBjM3CYWBrsWs640PVXmkuypo7S8rBHEv_z1jP3hhFqQzlI9L1s2DTQ6FYZwfop-xlA"
clarified = message.clarify( obfuscated ) # "if you use your imagination, this is a long block of text"

Ruby: importing two modules/classes of the same name

When my system requires two classes or modules of the same name, what can I do to specify which I mean?
I'm using rails (new to it), and one of my models is named "Thread". When I try to refer to the class "Thread" in thread_controller.rb, the system returns some other constant of the same name.
<thread.rb>
class Thread < ActiveRecord::Base
def self.some_class_method
end
end
<thread_controller.rb>
class ThreadController < ApplicationController
def index
require '../models/thread.rb'
#threads = Thread.find :all
end
end
When I try Thread.find(), I get an error saying that Thread has no method named find. When I access Thread.methods, I don't find my some_class_method method among them.
Any help? (And don't bother posting "just name your model something else." It's not helpful to point out obvious compromises.)
You could put your app into its own namespace.
<my_app/thread.rb>
module MyApp
class Thread
end
end
No really, name your model something else.
Thread is a reserved constant in Ruby and overriding that constant is only going to make you run into trouble. I compromised for my application and called it Topic instead.
If you absolutely must overwrite an existing constant, you can do something like this:
# use Object to make sure Thread is overwritten globally
# use `send` because `remove_const` is a private method of Object
# Can use OldThread to access already existing Thread
OldThread = Object.send(:remove_const, :Thread)
# define whatever you want here
class MyNewThread
...
end
# Now Thread is the same as MyNewThread
Object.send(:const_set, :Thread, MyNewThread)
Obviously anything that relied on the pre-existing Thread would be busted unless you did some kind of monkey-patching.
Just because this kind of thing can be done, doesn't mean it should be. But in certain circumstances it can be handy, for example in tests you can override a remote data source with your own 'dumb' object.

Resources