I have the following model:
class User < ActiveRecord::Base
def stripe_plan_id
self.stripe_subscription.plan.id
end
def stripe_subscription
customer = Stripe::Customer.retrieve(self.stripe_customer_id)
customer.subscriptions.retrieve(self.stripe_subscription_id)
end
end
I've got the model attribute stripe_plan_id I don't want to it persist in the database using ActiveRecord, but I need to check this parameter a lot. I would like to cache it on my server, and flush it on demand. Ideally I don't want to use redis.
What's the best approach? I'm trying to work out if ||= can be used somehow.
There are alot of ways, i'm assuming you know how to cache already using whatever you have (memcached, redis, rails.cache, etc), let me know if that is not the case. I recommend creating a caching class that gets or reloads cache based on an id. Your memoizing wouldn't work for your case because you want to persist between controllers.
def StripeCache < Struct.new(:user)
def cache_key
"#{user.id}_stripe_plan_id"
end
def get
ReadFromCache(cache_key)
end
def reload
WriteToCache(cache_key, user.stripe_plan_id)
end
end
Then when you want to get this you just call
StripeCache.new(user).get
and to save new stuff you call
StripeCache.new(user).reload
Is a little extra overhead, but will abstract all the reading and writing to and from cache from you.
Related
So I maintain a Rails app with more than 150 database tables. And we are experiencing deadlocks at several locations.
After reading through this post https://hackernoon.com/troubleshooting-and-avoiding-deadlocks-mysql-rails-766913f3cfbc and understanding better the different situations. it seems one common pattern we have is due to unique index waiting for each others on concurrent lock.
So I am looking for a way to say in a model that it should not try to insert two at the time, since MySQL will lock the table. I want it as easy as.
class BingoCard < ActiveRecord::Base
protect_table_locks
end
Which would use a Redis base lock, to wrap around the create operations
I already looked into this answer for ideas. Mutex for ActiveRecord Model
I plan on posting my own answer when I have it.
This is my draft implementation.
if there is enough interest, I will make it a gem
# frozen_string_literal: true
module ActiveRecord
module PersistenceRedisLock
private
def _create_record
_lock_manager.lock(_locked_resource_id, _lock_duration) do |_lock_info|
super
end
end
def _locked_resource_id
#TODO: make it a configurable option
"PersistenceRedisLock#{self.class.table_name}"
end
def _lock_duration
#TODO: make it a configurable option
10.seconds # Maybe too long of a default, but this is a proof of concept for now
end
def _lock_manager
##_lock_manager ||= Redlock::Client.new [Ph::Redis.redis_url_for(:red_locks)]
end
end
class Base
def self.protect_table_locks
self.prepend PersistenceRedisLock
end
end
end
In my application I am using one global variable which is defined in config/initializers/details.rb
$available_plans= Plan.all
and using this variable, all over the application and this will be available to all users. If I add one more plan, that should be reflected automatically, so I need to reload initializer
so after_create added a method to reload the initializer,
ActiveSupport::Dependencies.load_file "config/initializers/details.rb"
is this the right way ?
Why not:
class Plan < ActiveRecord::Base
after_create: custom_method
# Whatever code
private
def custom_method
$available_plans= Plan.all
end
end
Also, when a plan is edited that should be reflected in the global variable, so we should use after_save instead:
class Plan < ActiveRecord::Base
after_save: custom_method
# Whatever code
private
def custom_method
$available_plans= Plan.all
end
end
Another way to handle this is to forgo the initializer and global variables entirely. This, to my eye, is more localized and cleaner.
First, in your Plan class, add a caching method to access all the plans:
def self.available_plans
#available_plans ||= Plan.all
end
Note: This can be improved using the cached_method gem which will let you cache the plans across app instances etc... if that is important to you.
Now you can access your plans throughout the code like this:
Plan.available_plans
The first time this is called in an app instance, the plans will be loaded. Every time thereafter it will return the original results.
Finally, when plans are added, removed, or modified (again, in your Plan model):
after_save :expire_cached_available_plans
after_destroy :expire_cached_available_plans
...
private
def expire_cached_available_plans
self.class.instance_variable_set("#available_plans", nil)
end
Now whenever you create, update, or destroy an Plan object, the cached plans will be cleared. The next time you call Plan.available_plans, it will fetch (and cache) a fresh list.
In my rails app I would like to track who changes my model and update a field on the model's table to reflect.
So, for example we have:
class Foo < ActiveRecord::Base
before_create :set_creator
belongs_to :creator, :class_name => "User"
protected
def set_creator
# no access to session[:user_id] here...
end
end
What's a good testable way for me to get at the user_id from my model? Should I be wacking this data in Thread.current ?
Is it a better practice to hand this information from the controller?
Best practice in MVC is to have your Models be stateless, the controller gets to handle state. If you want the information to get to your models, you need to pass it from the controller. Using a creation hook here isn't really the right way to go, because you are trying to add stateful data, and those hooks are really for stateless behavior.
You can pass the info in from the controller:
Foo.new(params[:foo].merge {:creator_id => current_user.id})
Or you can create methods on User to handle these operations:
class User
def create_foo(params)
Foo.new(params.merge! {:creator_id => self.id})
end
end
If you find yourself writing a lot of permissions code in the controller, I'd go with option 2, since it will let you refactor that code to the model. Otherwise option 1 is cleaner.
Omar points out that it's trickier to automate, but it can still be done. Here's one way, using the create_something instance method on user:
def method_missing(method_sym, *arguments, &block)
meth = method_sym.to_s
if meth[0..6] == "create_"
obj = meth[7..-1].classify.constantize.new(*arguments)
obj.creator_id = self.id
else
super
end
end
You could also override the constructor to require user_ids on construction, or create a method inside ApplicationController that wraps new.
There's probably a more elegant way to do things, but I definitely don't like trying to read state from inside Model code, it breaks MVC encapsulation. I much prefer to pass it in explicitly, one way or another.
Yeah, something like that would work, or having a class variable on your User model
cattr_accessor :current_user
Then in your controller you could have something like:
User.current_user = current_user
inside a before filter (assuming current_user is the logged in user).
You could then extend AR:Base's create/update methods to check for the existence of a created_by/updated_by field on models and set the value to User.current_user.
I'd create new save, update, etc methods that take the user_id from everything that calls them (mainly the controller).
I'd probably extend ActiveRecord:Base into a new class that handles this for all the models that need this behaviour.
I wouldn't trust Thread.current, seems a bit hackish. I would always call a custom method which takes an argument:
def create_with_creator(creator, attributes={})
r = new(attributes)
r.creator = creator
r.save
end
As it follows the MVC pattern. The obviously inherient problem with this is that you're going to be calling create_with_creator everywhere.
You might find PaperTrail useful.
Probably you could check out usertamp plugins, found two in github
http://github.com/delynn/userstamp/tree/master
http://github.com/jnunemaker/user_stamp/tree/master
I'm working on a multi-user, multi-account App where 1 account can have n users. It is very important that every user can only access info from its account. My approach is to add an account_id to every model in the DB and than add a filter in every controller to only select objects with the current account_id. I will use the authorization plugin.
Is this approach a good idea?
What is the best way to always set the account_id for every object that is created without writing
object.account = #current_account
in every CREATE action? Maybe a filter?
Also I'm not sure about the best way to implement the filter for the select options. I need something like a general condition: No matter what else appears in the SQL statement, there is always a "WHERE account_id = XY".
Thanks for your help!
This is similar to a User.has_many :emails scenario. You don't want the user to see other peoples emails by changing the ID in the URL, so you do this:
#emails = current_user.emails
In your case, you can probably do something like this:
class ApplicationController < ActionController::Base
def current_account
#current_account ||= current_user && current_user.account
end
end
# In an imagined ProjectsController
#projects = current_account.projects
#project = current_account.projects.find(params[:id])
I know, I know, if you access Session-variables or Instance variables in your Model you didn't understand the MVC pattern and "should go back to PHP". But still, this could be very useful if you have - like us - a lot of controllers and actions where you don't always want to write #current_account.object.do_something (not very DRY).
The solution I found is very easy:
Step 1:
Add your current_account to Thread.current, so for example
class ApplicationController < ActionController::Base
before_filter :get_current_account
protected
def get_current_account
# somehow get the current account, depends on your approach
Thread.current[:account] = #account
end
end
Step 2:
Add a current_account method to all your models
#/lib/ar_current_account.rb
ActiveRecord::Base.class_eval do
def self.current_account
Thread.current[:account]
end
end
Step 3: Voilá, in your Models you can do something like this:
class MyModel < ActiveRecord::Base
belongs_to :account
# Set the default values
def initialize(params = nil)
super
self.account_id ||= current_account.id
end
end
You could also work with something like the before_validation callback in active_record and then make with a validation sure the account is always set.
The same approach could be used if you always want to add the current_user to every created object.
What do you think?
To answer your second question, check out the new default_scope feature in Rails 2.3.
I understand that you don't want to bother about scoping you account all time. Lets be honest, it's a pain in the a**.
To add a bit magic and have this scoping done seamlessly give a look at the following gem
http://gemcutter.org/gems/account_scopper
Hope this helps,
--
Sebastien Grosjean - ZenCocoon
I've pretty much tried everything, but it seems impossible to use
expire_fragment from models? I know you're not supposed to and it's
non-MVC, but surely there much be some way to do it.
I created a module in lib/cache_helper.rb with all my expire helpers,
within each are just a bunch of expire_fragment calls. I have all my
cache sweepers setup under /app/sweepers and have an "include
CacheHelper" in my application controller so expiring cache within the
app when called via controllers works fine.
Then things is I have some external daemons and especially some
recurring cron tasks which call a rake task that calls a certain
method. This method does some processing and inputs entries into the
model, after which I need to expire cache.
What's the best way to do this as I can't specify cache sweeper within the model.
Straight up observers seem to be the best solution but then it
complains about expire_fragment being undefined etc etc, I've even
tried including the ActionController caching classes into the observer
but that didn't work. I'd love some ideas of how to create a solution
for this. Thanks.
Disclaimer: My rails is a bit rusty, but this or something like it should work
ActionController::Base.new.expire_fragment(key, options = nil)
The solution provided by Orion works perfectly. As an enhancement and for convenience, I've put the following code into config/initializers/active_record_expire_fragment.rb
class ActiveRecord::Base
def expire_fragment(*args)
ActionController::Base.new.expire_fragment(*args)
end
end
Now, you can use expire_fragment on all instances of ActiveRecord::Base, e.g. User.first.expire_fragment('user-stats')
This is quite easy to do. You can implement Orion's suggestion, but you can also implement the broader technique illustrated below, which gives you access to the current controller from any model and for whichever purpose you decided to break MVC separation for (e.g. messing with the fragment cache, accessing current_user, generating paths/URLs, etc.)
In order to gain access to the current request's controller (if any) from any model, add the following to environment.rb or, much preferably, to a new plugin (e.g. create vendor/plugins/controller_from_model/init.rb containing the code below):
module ActiveRecord
class Base
protected
def self.thread_safe_current_controller #:nodoc:
Thread.current[:current_controller]
end
def self.thread_safe_current_controller=(controller) #:nodoc:
Thread.current[:current_controller] = controller
end
# pick up the correct current_controller version
# from ##allow_concurrency
if ##allow_concurrency
alias_method :current_controller, :thread_safe_current_controller
alias_method :current_controller=, :thread_safe_current_controller=
else
cattr_accessor :current_controller
end
end
end
Then, in app/controllers/application.rb,
class ApplicationController < ActionController::Base
before_filter { |controller|
# all models in this thread/process refer to this controller
# while processing this request
ActiveRecord::Base.current_controller = controller
}
...
Then, from any model,
if controller = ActiveRecord::Base.current_controller
# called from within a user request
else
# no controller is available, didn't get here from a request - maybe irb?
fi
Anyhow, in your particular case you might want to inject code into your various ActiveRecord::Base descendants when the relevant controller classes load, so that the actual controller-aware code still resides in app/controllers/*.rb, but it is not mandatory to do so in order to get something functional (though ugly and hard to maintain.)
Have fun!
In one of my scripts I use the following hack:
require 'action_controller/test_process'
sweepers = [ApartmentSweeper]
ActiveRecord::Base.observers = sweepers
ActiveRecord::Base.instantiate_observers
controller = ActionController::Base.new
controller.request = ActionController::TestRequest.new
controller.instance_eval do
#url = ActionController::UrlRewriter.new(request, {})
end
sweepers.each do |sweeper|
sweeper.instance.controller = controller
end
Then, once the ActiveRecord callbacks are called, sweepers are able to call expire_fragment.
I'm a bit of a rails noob, so this may not be correct, or even helpful, but it seems wrong to be trying to call controller actions from within the model.
Is it not possible to write an action within the controller that does what you want and then invoke the controller action from within your rake task?
Why not have your external rake tasks call the expiry method on the controller. Then you're still being MVC compliant, you aren't building in a dependence on some scoping hack, etc.
For that matter, why don't you just put all the daemon / external functionality on a controller and have rake / cron just call that. It would be loads easier to maintain.
-- MarkusQ
Will it not be easier and clean just to pass the current controller as an argument to the model method call? Like following:
def delete_cascade(controller)
self.categories.each do |c|
c.delete_cascade(controller)
controller.expire_fragment(%r{article_manager/list/#{c.id}.*})
end
PtSection.delete(self.id)
controller.expire_fragment(%r{category_manager/list/#{self.id}.*})
end
You can access all public methods and properties of the controller from within model.
As long as you do not modify the state of the controller, it should be fine.
This might not work for what you're doing, but you may be able to define a custom call back on your model:
class SomeModel < ActiveRecord::Base
define_callback :after_exploded
def explode
... do something that invalidates your cache ...
callback :after_exploded
end
end
You can then use a sweeper like you would normally:
class SomeModelSweeper < ActionController::Caching::Sweeper
observe SomeModel
def after_exploded(model)
... expire your cache
end
end
Let me know if this is useful!