Ruby on Rails Global Class Variable not updating when changed - ruby-on-rails

I want to add a slight extension to a class in a ruby gem.
The gem I am using is private_pub : https://github.com/ryanb/private_pub and the class I am overriding is : https://github.com/ryanb/private_pub/blob/master/lib/private_pub/faye_extension.rb
I am have placed my implementation in a new file in config/initializers called faye_extension.rb. The piece I have added to the code is ##clients and the get_list_of_subscribers class method. The code in faye_extension.rb is as follows:
PrivatePub::FayeExtension
module PrivatePub
# This class is an extension for the Faye::RackAdapter.
# It is used inside of PrivatePub.faye_app.
class FayeExtension
# Callback to handle incoming Faye messages. This authenticates both
# subscribe and publish calls.
##### MY NEW BIT
##clients = 0
def self.get_list_of_subscribers
##clients
end
####
def incoming(message, callback)
##clients = ##clients + 1
if message["channel"] == "/meta/subscribe"
authenticate_subscribe(message)
elsif message["channel"] !~ %r{^/meta/}
authenticate_publish(message)
end
callback.call(message)
end
private
# Ensure the subscription signature is correct and that it has not expired.
def authenticate_subscribe(message)
subscription = PrivatePub.subscription(:channel => message["subscription"], :timestamp => message["ext"]["private_pub_timestamp"])
if message["ext"]["private_pub_signature"] != subscription[:signature]
message["error"] = "Incorrect signature."
elsif PrivatePub.signature_expired? message["ext"]["private_pub_timestamp"].to_i
message["error"] = "Signature has expired."
end
end
# Ensures the secret token is correct before publishing.
def authenticate_publish(message)
if PrivatePub.config[:secret_token].nil?
raise Error, "No secret_token config set, ensure private_pub.yml is loaded properly."
elsif message["ext"]["private_pub_token"] != PrivatePub.config[:secret_token]
message["error"] = "Incorrect token."
else
message["ext"]["private_pub_token"] = nil
end
end
end
end
When deployed into my application, the incoming() method of the FayeExtension code is called multiple times, however if in a view somewhere I use the following line of code:
<h3><%= PrivatePub::FayeExtension.get_list_of_subscribers.to_s %></h3>
to call my get_list_of_subscribers class method, it always returns 0, despite me calling incoming() 5 times, where I would expect it to output 5. So it seems my ##clients = ##clients +1 code inside of incoming() is not referencing or updating correctly my global variable.
How can I alter the code to achieve this?

##clients might be a class variable, but it is not shared between different processes. Applications deployed to production usually run multiple processes.
You will need to store that value somewhere where each process has access to: Your database, redis, memcache...

Related

Rails using ActiveSupport::Cache::MemoryStore to save variable between two api calls (controller methods)

I have APIs where I want to save some values that would be accessible between each API method, so I am trying to use the ActiveSupport::Cache::MemoryStore. I'm probably not using MemoryStore correctly (never used it, cannot find a specific tutorial for it)
Am I using MemoryStore correctly? Seems like it should be super simple, but can't seem to save any value.
class MyController
# 1st API handler /post
def first
#cache = ActiveSupport::Cache::MemoryStore.new() if #cache.nil?
#cache.write('shared_val', params['user_key'])
end
# 2nd API handler /post
def second
#cache = ActiveSupport::Cache::MemoryStore.new() if #cache.nil?
saved_val = #cache.read('shared_val')
puts "#{saved_val}" # nil?????
end
I also tried some examples I saw in SO answers, but still cannot seem to save the value.
# In config file
config.cache_store = :memory_store
# 1st API handler /post
def first
Rails.cache.write("ABC", "abc")
check_val = Rails.cache.read('ABC')
puts "VALUE: #{check_val}" # shows correct 'abc'
end
# 2nd API handler /post
def second
Rails.cache.fetch("ABC") # gets nil, why???
Rails.cache.read("ABC) # also nil
end
config.action_controller.perform_caching is false by default. You need to turn it on in other environments.

Rails override default scope global

I have a Rails 3.2 application, and I want to use one database for many clients and one application. So for every model I have created a field called account_id, now I want to add a global scope for filtering the row in the base of the account_id of the logging user(account_id is a session param). So in initialize I have created a file and put these code
module ActiveRecord
# = Active Record Named \Scopes \
module Scoping
module Default
module ClassMethods
def unscoped #:nodoc:
return (block_given? ? relation.scoping { yield } : relation).where(account_id: Thread.current['account_id'].id)
end
def default_scope(scope = {})
scope = Proc.new if block_given?
if scope.kind_of?(Array) || scope.is_a?(Hash)
scope.merge!({:conditions=>{account_id:Thread.current['account_id'].id}})
end
self.default_scopes = default_scopes + [scope]
end
end
end
end
end
If I logged with user account_id=2 all is ok, but if in the same moment I logged on another browser or computer with account_id=3 ...I have many errors and on the log, I have seen that the application use account_id=2 but also account_id=3 at the same time.
Any solution? How I can rewrite default_scope(scope = {})? Other other idea?
Thread.current[] data is not unique per request. I used to have the same problem. By that time I had been using this gem https://github.com/steveklabnik/request_store. Hope it will help or at least give an idea.

Building Rack Middleware responses with Flash message functionality

I have a Sinatra app that's mounted on a Rails app under /admin. The Sinatra app is an admin dashboard, and therefore should only be available to authorized users.
To enforce that, I built a piece of Rack Middleware that will run before the Sinatra app is called.
The logic is simple -
If user is authenticated, continue as normal
If user is not authenticated, redirect to the root path with a flash alert message (I'm using the rack-flash gem to allow access to the flash messages in Rack)
Code below. I feel like I'm missing something in the redirect method. The Rack::Builder block constructs a mini-Rack application and the block inside further creates another Rack application (the Proc) that builds the Redirect Response with flash message.
When I run it, I get undefined method 'detect' for nil:NilClass, which indicates that neither block is returning a valid non-nil response. Do I need to run call somewhere on one of these blocks?
I'm using a Puma Webserver if that helps.
Thanks!
require "rack"
require "rack-flash"
class AdminAuthorizer
def initialize(app)
#app = app
end
def call(env)
#env = env
id = #env["rack.session"][:user_id]
user = User.where(id: id).first
# Check if user is authorized, otherwise redirect
user.admin? ? ok : redirect
end
private
def ok
#app.call(#env)
end
def redirect
Rack::Builder.new do
use Rack::Flash, sweep: true, accessorize: true
run(
Proc.new do |env|
env["x-rack.flash"].alert = "Insufficient permissions"
res = Rack::Response.new
res.redirect("/")
res.finish
end
)
end
end
end
Ok, figured it out myself for anyone else that's curious.
I had to use the env key 'action_dispatch.request.flash_hash', which is used by the Flash middelware here
I didn't have to use the rack-flash gem, although I'm sure that's still useful when building Sinatra apps and such
NOTE: This is on Rails v4.2.4. I believe there have been several changes to that Flash module since, so I don't know if that key has changed. But you can confirm by searching the latest repo for a similar definition.
require "rack"
class AdminAuthorizer
FLASH = ActionDispatch::Flash
def initialize(app)
#app = app
end
def call(env)
#env = env
id = #env["rack.session"][:user_id]
user = User.where(id: id).first
# Check if user is authorized, otherwise redirect
user.admin? ? ok : redirect
end
private
def ok
#app.call(#env)
end
def redirect
# Calls a Rack application (the defined Proc). If you want to do more steps
# or get fancier, you can wrap this in a Rack::Builder call
#
# Rack::Builder.app(redirect_proc)
# use (blah)
# run (blah)
# end.call(#env)
#
redirect_proc.call(#env)
end
def redirect_proc
Proc.new do |env|
# Use the key 'action_dispatch.request.flash_hash' to set
# an new FlashHash object. This will overrite any existing FlashHash
# object, so use it carefully. You can probably avoid that by checking
# for an existing one and adding to it.
env[FLASH::KEY] = FLASH::FlashHash.new(alert: "Insufficient permissions")
# Construct the redirect response
res = Rack::Response.new
res.redirect("/")
res.finish
end
end
end

Rails MonkeyPatch changes not being picked up

I'm trying to 'MonkeyPatch' this controller in my implementation so that it can handle a third parameter ('productname').
The original activate method in the gem reads
def activate
if Digest::MD5.hexdigest(params["security_data"] + SaasySimple.config.secret) == params["security_hash"]
SaasySimple.config.model.activate( params['token'], params['id'] )
end
end
My entire new file, placed in lib/monkeys/sassysimple.rb, reads
module SaasySimple
class SubscriptionsController < ApplicationController
def activate
if Digest::MD5.hexdigest(params["security_data"] + SaasySimple.config.secret) == params["security_hash"]
SaasySimple.config.model.activate( params['token'], params['id'], params['productname'] )
end
end
end
end
This isn't working - I'm still getting an error of subscriptions#activate (ArgumentError) "wrong number of arguments (2 for 3)", which I believe is caused because my user method (see below) is expecting productname but not getting it from the un-monkeypatched version. Can someone tell me why putting the file in lib isn't working? Thanks!
This is the user method:
def self.activate(token, id, productname)
user = User.find( id )
user.token = token
user.status = 'active'
user.package = productname
user.save!
end
I'd put the monkeypatch in an config/initializers/sassysimple.rb
Have you verified that the controller really sees all three params?
Is User#activate called anywhere else in the code? e.g. grep for it.

Disable certain emails in ActionMailer

I would like to turn off certain emails in development and on test/staging servers, but keep sending them in production. What I currently do is set the default "to" address for these administrative mails to blank:
default :to => Rails.env.production? ? "admin-list#sharethevisit.com" : ""
However, this puts stack traces in my development log when I'm testing the user mails which should be sending.
Is there a more effective way of disabling certain emails based on the current environment? I tried checking in the functions themselves, but is not ideal because I have to change each function, plus it doesn't actually work... it just fails to set the needed #account variable before rendering the email and creating a different stack trace.
def user_registered_notify_email(account)
if Rails.env.production?
#account = account
mail(:subject => "New user registered: #{#account.full_name}")
end
end
I usually use mail interceptors as described here: http://asciicasts.com/episodes/206-action-mailer-in-rails-3
I can't remember where I found this to credit the author but this is how I redirected email in development mode. Create a new file in RAILS_ROOT/config/initializers for this. DEFAULT_DEV_EMAIL_OVERRIDE is defined in our main site config with other static values.
if Rails.env.development?
if Rails.version =~ /^2\./
class ActionMailer::Base
def create_mail_with_overriding_recipients
mail = create_mail_without_overriding_recipients
mail.to = DEFAULT_DEV_EMAIL_OVERRIDE
mail
end
alias_method_chain :create_mail, :overriding_recipients
end
elsif Rails.version =~ /^3\./
if Rails.env.development?
class OverrideMailRecipient
def self.delivering_email(mail)
mail.to = DEFAULT_DEV_EMAIL_OVERRIDE
end
end
ActionMailer::Base.register_interceptor(OverrideMailRecipient)
end
end
end
Consider using an interceptor to set Mail::Message#perform_deliveries to false:
class NeverDeliverInterceptor
def self.delivering_email(message)
message.perform_deliveries = false
end
end
if !Rails.env.production?
ActionMailer::Base.register_interceptor(NeverDeliverInterceptor)
end
See API doc for source & other usage.

Resources