Creating smart model in Ruby - ruby-on-rails

I want to create an ActiveRecord-like interface for Salesforce, such that I can call
class Account < Salesforce::Model
end
and be able to call methods like Account.find_by_FirstName() using my own method_missing function.
However, connecting to Salesforce can be done in two ways: username and password, and oauth. If a username/password is used, I can have it defined in a salesforce.yml file and load automatically. But with oauth, I can't do that since each user will have this defined. I don't want to initialize a class with Account.new('oauth', oauth_parmas) or Account.new('username','password','sec_token'), but have the model determine which to use based off of rules and by seeing if one or the other is present.
Is there a way to implement this? In other words, is there a way for the model to know if the current user has a current oauth token or if a username/password defined?
Additionally, if I were to use this in a Rails app, the user would be logging in after the app was started, so the oauth token would be defined after the application started, and would be different for each of the multiple users. For example, let's say I call Account.find_by_FirstName('John') in AccountController#Show. I want the Account model to use the oauth token or usename/password without having to be asked. I also don't want to establish connection directly in my show method in the controller. I have two questions:
How would I implement this? Should I use a before_filter in the controller, or is there a way to implement this application-wide?
If I have multiple users connecting to Salesforce, would this cause issues in my application? In other words, would I have to worry about a connection being used by another user since the connection is dynamic?

Your needing is not different from ActiveRecord::Base connection establishment: you establish the connection using ActiveRecord::Base.establish_connection and every model you use after the connection establishment know which connection to use, because you memorized the connection at superclass level.
For Salesforce you can use the same concept:
class Salesforce::Model
def self.oauth_params
#oauth_params
end
def self.establish_connection(oauth_params)
#oauth_params = oauth_params
end
def self.find(id)
# use oauth_params here
end
end
class Account < Salesforce::Model
end
Now you can do something like
Salesforce::Model.establish_connection ['username', 'password']
Account.find 2 # without specifying authentication params
Since you know authentication params after knowing the logged user, you can establish the connection after the user is logged:
def sign_user
# user = ...
oauth_params = get_oauth_params(user)
Salesforce::Model.establish_connection(oauth_params)
end
Concurrency (threads)
If I have multiple users connecting to Salesforce, would this cause issues in my application? In other words, would I have to worry about a connection being used by another user since the connection is dynamic?
Legitimate question. If you run the Rails application in a threaded environment (threaded application server - f.e. Puma, multi-threaded architecture - JRuby, Rubinius...) AND Rails is configured as threadsafe (config.threadsafe!), you could have concurrency problems (the explanation is not trivial - check out this).
If this is your case you can scope the #oauth_params variable accessor to Thread.current:
class Salesforce::Model
#oauth_params = { Thread.current => nil }
def self.oauth_params
#oauth_params[Thread.current]
end
def self.establish_connection(oauth_params)
#oauth_params[Thread.current] = oauth_params
end
Would it be possible that the thread for the current user changes?
It is possible, if some code you execute runs inside a new thread. F.e.:
Salesforce::Model.establish_connection(oauth_params)
Thread.new{ p Salesforce::Model.oauth_params }.join #=> puts nil
In this case you have to reestablish the connection in the new thread (I can do it just if you need it).
I could request something on thread 1 and complete that request, but afterwards, someone else uses thread 1 and I have to use thread 2. Is this possible?
Thinking about it, you need to reset the variable at the beginning of the call in order to avoid that the next request uses the params set in any previous request:
before_action :reset_connection, :sign_user
def reset_connection
Salesforce::Model.establish_connection(nil)
end
def sign_user
# ...

Related

Is it safe to use Thread.current with Unicorn in Rails?

I need to get current login user name in model code, but I dont want to add a new additional parameter that will require many changes. So I am thinking whether it works to put the login user name in Thread.current, and then access it in model code.
It works in a simple try, but I have a concern whether it can work properly with unicorn multi workers, for example
- the login request is handled by worker 1, and the 2nd request is handled by worker 2. My basic understanding is that it should be ok because I set it from session into Thread.current in ApplicationController before filter that should be executed in the beginning of each request.
- if a unicorn worker is killed and restarted for whatever reason, is the request will be re-initiated, and still have the session data?
I dont have enough knowledge on unicorn... so probably it is a naive question...
And any other possible issue to use Thread.current?
Thanks in advance for your help!
You don't have to use Threads directly, you can use this gem https://github.com/steveklabnik/request_store
Your User model code can look something like this:
def self.current_user
RequestStore.store[:current_user]
end
def self.current_user=(logged_in_user)
RequestStore.store[:current_user] = logged_in_user
end
And in your controller, after login you can set User.current_user = current_user

How to set variables across models for a single request in rails?

The scenario: I need to give models access to API tokens stored in the session.
Background: I have an API-driven rails 3 application utilizing DataMapper(DM) and a DM adapter to interface with the API. Each DM model has a corresponding REST-ish API endpoint much like you get with rails scaffolding. The API requires various headers for requests, including API tokens, keys, ids etc. The headers have nothing to do with the requested data, they exist for authorization and tracking purposes only. A number of these tokens are stored in the session. I want a clean way to make these API headers available to any model during a request.
Possible solutions:
1. Passing session variables from the controller to the models
The obvious answer is passing the tokens in a hash or other object from the controller to the models. A controller action might have the following: #user = User.find(params[:id], api_headers).
The problem is needing to override any model method to accept the additional api_headers object. Not counting methods defined by Rails and DataMapper, there are hundreds of methods already defined in the application models that would need to be rewritten. So I'm ruling out a rewrite, and this also doesn't seem like a good solution since it would require overriding a ridiculous number of DM methods like the User#find example above.
2. Some metaprogramming hack
I could catch any ArgumentError's on DM's base class and check if the last argument is the api_headers object, then set the values as instance variables and invoke the requested method. This thought exercise already has me cringing at dealing with optional arguments etc. If given long enough I could probably create a functional Frankenstein that should get me fired but probably wouldn't.
3. Use a singleton (current preferred solution)
In the application controller set a before_filter to dump the session-stored API headers into a singleton ApiHeaders object. Then any model making an API request can get that singleton with the required API headers.
An additional after_filter* on the application controller would set all attributes to nil on the ApiHeaders singleton at the end of the request to prevent leaking headers between requests.
This is currently my preferred solution but I don't like that the API header values could potentially carry over into other requests if the after_filter doesn't get invoked. I don't know in which scenarios this might happen (in an application error perhaps?) which raises concerns. All I know is the values don't necessarily die with the request.
4. Custom code
Drop support of DataMapper and the custom API adapter and manually make all API calls, passing through all required API headers. Besides the fact I don't have time for this level of rewrite, why use a framework at all if you have to throw a huge chunk out to support a custom auth scheme?
Summary
What's the cleanest way to get these pesky API tokens from the session into the bowels of the application where they can be sent with each API request? I'm hoping for a better solution than those listed above.
* An alias for after_action
I set the current user and the request information on my User model using the request_store gem which is just a tiny shim over thread local storage with a bit of clean-up.
This makes the information available from any of my models via the User class. I have User.current, User.request and User.location available wherever I need it.
Your controller just has to set User.current and User.request once it has authenticated the user.
Example User model:
# models/user.rb
require 'request_store'
class User
def self.current
RequestStore.store[:current_user]
end
def self.current=(user)
RequestStore.store[:current_user] = user
end
def self.request
RequestStore.store[:current_request]
end
def self.request=(request)
# stash the request so things like IP address and GEO-IP based location is available to other models
RequestStore.store[:current_request] = request
end
def self.location
# resolve the location just once per request
RequestStore.store[:current_location] ||= self.request.try(:location)
end
end
Use Thread.current, which is passed in from request to model (note, this breaks if, inside your request, you use sub-threads). You can store the attribute you want to share in a cattr_accessor or in rails cache:
in a cattr_accessor
class YourClass
cattr_accessor :my_var_hash
...
# and in your controller
# set the var
YourClass.my_var_hash = {} if YourClass.my_var_hash.nil?
YourClass.my_var_hash[Thread.current.object_id] = {}
YourClass.my_var_hash[Thread.current.object_id][your_var] = 100
... and in your model
lvalue = YourClass.my_var_hash[Thread.current.object_id][your_var]
Note, if you use this method, you will also want to make one of the hash values a timestamp, and do some housekeeping on getting, by deleting old keys, b/c you'll eventually use up all your system memory if you don't do the housekeeping
with cache:
# in your controller
#var = Rails.cache.fetch("#{Thread.current.object_id}_var_name") do
return 100 # do your work here to create the var value and return it
end
# in your model
lvalue = Rails.cache.fetch(("#{Thread.current.object_id}_var_name")
You can then set the cache expiration to 5 minutes, or you can wildcard clear your cache at the end of your request.

Rails: Every user has a own database user

My customer has a existing database where every user has it's own database user and he wants that every user uses his own database user to log into the rails application.
For my models of this database I use ActiveRecord::Base::establish_connection to connect with the username and password of the login form.
Now I thought to close this connection in an after_filter so no other user can get this connection from the connection pool. But this doen't work.
I want something like this in my controller:
class Demo < ActiveRecord::Base
before_filter :connect
after_filter :close_connection
def action
# do some stuff with the database
end
end
How can I achive this with ActiveRecord or is there a better solution for this problem? Or is there an other ORM mapper that my be better for this problem?
I can't change the database or the fact that every user has its own database user that he needs to login becuase my customer does use this structure for other applications :(
It's a Postgres database. And Rails 3.2.13 (JRuby).
First off, I've never developed this kind of authentication system, but if I had to I would do probably choose Warden, which is an authentication system integrated as a rack middleware.
Warden allows to build your own authentication strategies. So I made an example for your case, but be aware that it does not work. It is just for the sake of clarity.
The idea is to reconfigure your database pool with a given username/password in a rack middleware. Rack middlewares are the first components called in a request, so I think it should avoid performance issues.
Warden::Strategies.add(:database_user) do
def valid?
ActiveRecord::Base.connected?
end
def authenticate!
# Disconnects all connections in the pool, and clears the pool.
ActiveRecord::Base.connection_pool.disconnect!
# Reconfigure the pool with the new username/password
config = Rails.application.config.database_configuration[Rails.env]
config['username'] = params[:username]
config['password'] = params[:password]
# Establish connection with the new configuration
ActiveRecord::Base.establish_connection(config)
# Check if the connection works
# It means that the username/password is correct
if ActiveRecord::Base.connected?
user = build_user(params[:username])
success!(user)
else
fail!("Wrong username/password")
end
rescue # Errors thrown by Postgres
fail!("Wrong username/password")
end
private
def build_user(username)
OpenStruct.new({username: username}) # Dynamic object
end
end
Then configure Warden in your Rails configuration file.
Rails.configuration.middleware.use RailsWarden::Manager do |manager|
manager.default_strategies :database_user
manager.failure_app = LoginController
end
Once again this is just a prototype to give you an insight of how you could resolve your problem. I hope that will help you a little bit.
This is not a good choice to reset connection on each request per user. Most importantly it looses the db cache as you have to initiate connection in each request.
Better try to use Mongodb (if possible) where each record will belong to a user and everything of that user will be under that record. This solutions is possible if you are trying to develop a system. Existing system with giant db might consider this solution impossible :)

How to handle Shopify API connection with Shopify gem?

Hi I'm using the Shopify gem in my Shopify app and I'm looking for suggestions on how to handle the API connection to Shopify.
I'm using webhooks and delayed_jobs so I need a way to open the connection outside of the controller.
At the moment I added this method to my Shop model:
def connect_to_store
session = ShopifyAPI::Session.new(self.url, self.access_token)
session.valid?
ShopifyAPI::Base.activate_session(session)
end
So I can open the connection very easily, for example:
Shop.find(1).connect_to_store
ShopifyAPI::Shop.current.name
The problem is that, inside my Product module, I need the connection open inside several methods but I end up calling the connect_to_store method several times and I'm worried about opening several connections to the same store, without a real need.
Is there a way to check if a connection is already opened and open a new one only if another one is not found?
Thanks,
Augusto
------------------- UPDATE -------------------
I explain better my issue.
Let's say that in my Product model I want to see if a given product has a compare_at_price greater than its price and, in this case, I want to add a "sale" tag to the Shopify product.
In my Product model I have:
class Product < ActiveRecord::Base
belongs_to :shop
def get_from_shopify
self.shop.connect_to_store
#shopify_p = ShopifyAPI::Product.find(self.shopify_id)
end
def add_tag(tag)
#shopify_p = self.get_from_shopify
shopify_p_tags = shopify_p.tags.split(",")
shopify_p_tags.collect{|x| x.strip!}
unless shopify_p_tags.include?(tag)
shopify_p_tags << tag
shopify_p_tags.join(",")
shopify_p.tags = shopify_p_tags
shopify_p.save
end
end
def on_sale?
#shopify_p = self.get_from_shopify
sale = false
shopify_p.variants.each do |v|
unless v.compare_at_price.nil?
if v.compare_at_price > v.price
sale = true
end
end
end
return sale
end
def update_sale_tag
if self.on_sale?
self.add_tag("sale")
end
end
end
My problem is that if I call:
p.update_sale_tag
the Shop.connect_to_store is called several times and I authenticate several times while I'm already authenticated.
How would you refactor this code?
I approach this by storing the OAuth token that is returned by Shopify with the store (you should be doing this anyway). All you need to access the API is the token, so in your shop model you would have a method like:
def shopify_api_path
"https://#{Rails.configuration.shopify_api_key}:#{self.shopify_token}##{self.shopify_domain}/admin"
end
Then if you want to access the API for a particular store in a Delayed Job worker, you would simply:
begin
ShopifyAPI::Base.site = shop.shopify_api_path
# Make whatever calls to the API that you want here.
products = ShopifyAPI::Product.all
ensure
ShopifyAPI::Base.site = nil
end
Hopefully that helps a little. I find working with Sessions outside of controllers to be a bit messy, particularly since this is nice and easy.
Once your application has authenticated once, you can hold on to that computed password – it’s good until the app is uninstalled for that particular store.
In other words, authenticate just the once when the merchant first installs the app, save the password to a db, and load it up whenever you need it. Your self.shop.connect_to_store call should then just set the ShopifyAPI::Session instance.
I think there is some misunderstanding here. You do know that you are really just using Active Resource for all your API work? And therefore when you authenticate, you are probably authenticating a session? And that once authenticated, no matter how many times you actually use the API, you're not actually opening "new" connections.
You are doing it wrong if you are constantly authenticating in a single session to do more than one API call.
If you happen to be in a block of code that has no authentication (for example your App may process a WebHook from N shops) or a Delayed Job, simply pass the myshopify_domain string to those code blocks, look up the Shop in your DB, find the auth token, authenticate (once)... and away you go... it really quite simple.

Alternative to using Thread.current in API wrapper for Rails

I've developed an application that allows our customers to create their own membership protected websites. My application then connects to an outside API service (customer specific api_key/api_url) to sync/update/add data to this other service. Well, I've had an API wrapper written for this other service that has worked up to this point. However, I'm now seeing very random drops where the connection is nil. Here is how I'm currently using the connection:
I have a xml/rpc connection class
class ApiConnection
attr_accessor :api_url, :api_key, :retry_count
def initialize(url, key)
#api_url = url
#api_key = key
#retry_count = 1
end
def api_perform(class_type, method, *args)
server = XMLRPC::Client.new3({'host' => #api_url, 'path' => "/api/xmlrpc", 'port' => 443, 'use_ssl' => true})
result = server.call("#{class_type}.#{method}", #api_key, *args)
return result
end
end
I also have a module that I can include in my models to access and call the api methods
module ApiService
# Set account specific ApiConnection obj
def self.set_account_api_conn(url, key)
if ac = Thread.current[:api_conn]
ac.api_url, ac.api_key = url, key
else
Thread.current[:api_conn] = ApiConnection.new(url, key)
end
end
########################
### Email Service ###
########################
def api_email_optin(email, reason)
# Enables you to opt contacts in
Thread.current[:api_conn].api_perform('APIEmailService', 'optIn', email, reason)
end
### more methods here ###
end
Then in the application controller I create a new ApIConnection object on every request using a before filter which sets the Thread.current[:api_conn]. This is because I have hundreds of customers each with their own api_key and api_url, using the application at the same time.
# In before_filter of application controller
def set_api_connection
Thread.current[:api_conn] = ApiService.set_account_api_conn(url, key)
end
Well my question is that I've read that using Thread.current is not the most ideal way of handling this, and I'm wondering if this is the cause for the ApiConnection to be nil on random requests. So I would like to know how I could better setup this wrapper.
Answer 1
I'd expect that the problem is the next request coming before the connection has finished, and then the before_filter overwrites the connection for the still ongoing connection. I'd try to stay away from threads. It's easier to fork_off, but there's certain caveats to that as well, especially regarding performance.
I try to move logic like this over to a background job of some sort. A common solution is delayed job https://github.com/collectiveidea/delayed_job that way you don't have to mess with threads and it's more robust and easy to debug. You can then start background jobs to asynchronously sync the service whenever somebody logs in.
#account.delay.optin_via_email(email,user)
This will serialize the account, save it to the job queue, where it will be picked up by delayed job unserialized and the method after delay will be called. You can have any number of background jobs, and even some job queues dedicated to certain types of actions (via using job priorities - let's say two bj for high prio jobs and one dedicated to low prio jobs)
Answer 2
Just make it as an object instead
def before_filter
#api_connection = ApiConnection.new(url, key)
end
then you can use that connection in your controller methods
def show
#just use it straight off
#api_connection.api_perform('APIEmailService', 'optIn', email, reason)
# or send the connection as a parameter to some other class
ApiService.do_stuff(#api_connection)
end
Answer 3
The easiest solution might just be to create the api connection whenever you need it
class User < ActiveRecord::Base
def api_connection
# added caching of the the connection in object
# doing this makes taking a block a little pointless but making methods take blocks
# makes the scope of incoming variables more explicit and looks better imho
# might be just as good to not keep #conn as an instance variable
#conn = ApiConnection.new(url, key) unless #conn
if block_given?
yield(#conn)
else
#conn
end
end
end
that way you can easily just forget about the creation of the connection and have a fresh one handy. There might be performance penalities with this but I suspect that they are insignificant unless there's an extra login request
#user.api_connection do { |conn| conn.optin_via_email(email,user) }

Resources