I have a model along the lines of:
class Account < ActiveRecord::Base
has_many :payments
has_many :purchases
def balance
payments.sum(:dollar_amount) - purchases.map{|p| p.dollar_amount}.sum
end
end
I want to memoize the balance method and store it in memcached. The problem, of course, is that the cached value needs to get expired any time a payment or purchase is created. I could insert code in the after_save callbacks of payments and purchases to expire the cached balances of their accounts but it seems to me that it would be easier to understand/maintain if I could say something like:
cached_memoize :balance, :depends_on => [:payments, :purchases]
Is there an existing gem/plugin that does this? And before I go off and write my own, is it a good idea? The downside that I see is that it might make it less obvious to somebody who is modifying the dollar_amount method of Purchase that they need to take into account a caching issue (if they unwittingly introduced a dependency on another model, like SubPurchase or something, it would screw things up.) But since this isn't super obvious anyway, I think that having a neat declarative syntax is worth it - at least that way when it breaks, it's clear how to fix it.
Thoughts?
Edit: in response to semanticart's answer I will be more explicit about my problem with the "just put the expires in the relevant callback" approach - the problem is that you end up with expires all over the codebase - it starts with the after_save callback on payment, but maybe it's in a separate observer for purchases, and then you have polymorphic associations, inheritance trees, etc. The syntax I'm proposing forces developers to keep all these cases in a neat list in one place. That way when you get a bug report like "users balances sometimes are out of sync and they aren't quite sure how to replicate the issue" it is a lot easier to figure out what is going on.
I'd consider another approach: have a balance field on the account. Use callbacks (after_save, etc.) on the Purchase and SubPurchase models to update the balance field on the parent Account. Your balance only changes when the other models are modified and you never have to worry about it being stale.
sounds to me like you want to fork cache_fu, and add an option that magically sprinkles the after_saves across the dependent records. I dig having your dependencies in 1 place.
Not sure this is what you are looking for but it may help.
http://railscasts.com/episodes/137-memoization
Related
This is probably a complicated one, but I'll give it a try.
We have a Rails app - peer-to-peer marketplace with lots of registered users in beta testing. Users have many items for sale and swap. User will get offers from other users that want to buy or swap their items for user's items. Those items might also be wished by other users. Items have many likes and comments from other users and there are also notifications implemented for each action.
In short there's lots of one-to-many, many-to-many and polymorphic relations in Users and Items table.
Some sneak peak into model complexity:
class Item < ActiveRecord::Base
belongs_to :user
has_many :wishes, as: :wishable
has_many :listing_likes, foreign_key: 'listing_id', class_name: 'ListingLike'
The question is, what's the best way to approach user account deletion and keeping Data Integrity in this case. Hard deleting and dependent: destroy is not an option, because it will break the user experience to other registered users.
The only thing that comes to mind is to add deleted boolean field to users table and to create a check function if the user was deleted, including this function basically everywhere in the app just to make sure that this user wasn't soft deleted and to display a greyed out user name that is not clickable anymore. That approach will probably take too long to implement and will be pretty hard to maintain.
Any advice for better approach will be appreciated. Is there a common approach, best practice or just an advice for how to treat it the less painful way?
Thanks in advance
I think your initial thought of implementing some form of "soft delete" is the way to go. When you build a complex network-like data structure you can't (and often don't want) to remove a single piece.
You might be surprised at how easy this is to implement yourself, but you don't need to implement it yourself: check out Paranoia, which implements a fairly robust approach to soft deletion.
I have customers and invoices. The users can delete customers, but not invoices.
The problem is when a customer is deleted I can no longer do
invoice.customer.name
Because customer is nil.
I have some solutions on the drawing board
Save some info about the customer on the invoice instance when the
invoice is made.
Not deleting the customer, but hide it from the user with a boolean
field. That way the customer instance is still around.
I know this question might be closed because the answers might be opinionated. Rails is very opinionated on almost everything. On this problem, I have never seen a opinion, so:
What is the rails best practice in this case?
(If you have a better solution I'd like to see it)
If you'd like to set up a deleted_at flag, I'd recommend using the paranoia gem.
https://github.com/radar/paranoia
It is exactly what your second option is and implements all the scopes that you might need.
Your second solution seems to best IMO.
Rather than deleting the customer, introduce a DELETED flag on the customer database (then add it as a default_scope to ignore customers with DELETED = true)
this way you can leave the relationships intact. if you have loads and loads of customers, you might need to think about an archiving mechansim, but I wouldn't worry about that.
Guys I am becoming completely nut on this...and can't figure out at all how come out of all this troubles.
I really need an holy help, or at least some suggestions about tutorial and guides about this problematic.
I believe that this is a very common task in many app, but I can't find nothing that help in this sense, not on stackoverflow, not on google, not on other rails forums that I know...I start to feel like a dumb!!!
Problem
(1) Models (the models that are involved)
User.rb
Item.rb (is nested in user: e.g. user/3/item/5)
(2) What I'd like to do
The current_user (the user that is logged in) can create an Item, and in the same form can decide to share it or no with many other users.
In this way, the item will be visible and accessible also for this users that we shared it with.
I understand that what is going to be involved here is:
(a) has_many :through relationship between item and user
(b) a joint table that we can call sharing (with item_id and user_id)
(c) eventually using different name for the user and use a :class_name to point to user
This is not hard to implement (and there are plenty of examples around).
What is really a mess, instead, is how build the controller (item controller), in order to perform in one shot, the creation of a new item, and the sharing operation (this means setting up the parameter in the joint table, and eventually create as many records as many users we are sharing with - array?)
And the other problem, that obviously is related with the controller, will be the view...how put all this in a single form, and allow the current_user to just click the button, and perform the item creation + the sharing operation?
Last thing, but not the least, how I need to deal with the fact that User is the same model of current_user??
I really don't know what I need to read or look up in order to let this work, I would love to see some example code,but at the same time I'd love to really understand the logic in all this, in order to be able to replicate it in other scenarios.
Please someone help me...I can't really move on in my app development without doing this, and this is also a crucial part of all the project.
Thanks so much for every small bit of help that I will receive.
Ok I have solved all my problems...that was not an hard task at the end (it's always easy saying that when you solved the issue:))
The only doubt that I still have is with the rails name convention:
Can be possible that using a name like sharing, for the joint model, that the final -ing is not very well handled by rails?
After many tentativeness performed to try to fix my problem, I discovered that everything was set up properly, and that just changing the joint table name from sharing to share, let the magic works!!!
I'm making a Purchase model that handles site purchases, which will interact with the Payment Gateway. My question is about how to design the interface, whether I should use separate class methods to do the work, or patch into the AR lifecycle with callbacks.
At first I was doing something like Purchase.make_purchase(product, ...), as a class method. But this didn't seem great.
What I'm about to implement is a solution that uses the model lifecycle and callbacks to make the purchase and gateway transaction. Something like this:
#purchase = Purchase.new
#purchase.product = product
#purchase.user = current_user
if #purchase.save
else
end
I would then have a before_save callback that talks to the gateway:
before_save :transfer_funds
that can halt save if unsuccessful, setting #purchase.errors[:gateway_error]
I'm not sure this is the best way to go about this. Any advice?
I haven't used it yet so I can't give much insight but I'd take a look at ActiveMerchant if you haven't already. I'm not sure what your payment gateway is right now, but if you're not using it; you may get a few ideas.
EDIT I realize it didn't answer the question, I was just thinking it may give you some ideas if you weren't already using it.
I don't have direct experience with payment processing so you can take my opinions with a big grain of salt.
My big consideration with using the life cycle methods is that you may end up having to put extra logic in your transfer_funds method in this case that you may not otherwise need. For example, if a Purchase could be updated at a later time, then you're going to be calling your transfer_funds method every time it's updated.
I'm not sure if a Purchase has the concept of a pre-authorization followed by an actual charge, but I'd imagine the transfer_funds should only be called once? You may be able to move this to a before_create instead but that may only fix this one scenario.
I've also found that moving them to the lifecycle methods can often add a lot more logic into your model than may desirable. In the past, I've found that being more explicit in a controller action can sometimes save me headaches down the road even if it adds a step for me to do every place I need to transfer_funds for example.
I now try to keep my life cycle methods in the model class only related to updating the ActiveRecord model itself and not doing much extra work. If keeping it in your controller is not viable, I'd consider using an ActiveRecord::Observer to abstract out the logic associated with transfer_funds.
Hope this gives you some ideas.
I want to log user's actions in my Ruby on Rails application.
So far, I have a model observer that inserts logs to the database after updates and creates. In order to store which user performed the action that was logged, I require access to the session but that is problematic.
Firstly, it breaks the MVC model. Secondly, techniques range from the hackish to the outlandish, perhaps maybe even tying the implementation to the Mongrel server.
What is the right approach to take?
Hrm, this is a sticky situation. You pretty much HAVE to violate MVC to get it working nicely.
I'd do something like this:
class MyObserverClass < ActiveRecord::Observer
cattr_accessor :current_user # GLOBAL VARIABLE. RELIES ON RAILS BEING SINGLE THREADED
# other logging code goes here
end
class ApplicationController
before_filter :set_current_user_for_observer
def set_current_user_for_observer
MyObserverClass.current_user = session[:user]
end
end
It is a bit hacky, but it's no more hacky than many other core rails things I've seen.
All you'd need to do to make it threadsafe (this only matters if you run on jruby anyway) is to change the cattr_accessor to be a proper method, and have it store it's data in thread-local storage
I find this to be a very interesting question. I'm going to think out loud here a moment...
Ultimately, what we are faced with is a decision to violate a design-pattern acceptable practice in order to achieve a specific set of functionality. So, we must ask ourselves
1) What are the possible solutions that would not violate MVC pattern
2) What are the possible solutions that would violate the MVC pattern
3) Which option is best? I consider design patterns and standard practices very important, but at the same time if holding to them makes your code more complex, then the right solution may very well be to violate the practice. Some people might disagree with me on that.
Lets consider #1 first.
Off the top of my head, I would think of the following possible solutions
A) If you are really interested in who is performing these actions, should this data be stored in the model any way? It would make this information available to your Observer. And it also means that any other front-end caller of your ActiveRecord class gets the same functionality.
B) If you are not really interested in understanding who created a entry, but more interested in logging the web actions themselves, then you might consider "observing" the controller actions. It's been some time since I've poked around Rails source, so I'm not sure who their ActiveRecord::Observer "observes" the model, but you might be able to adapt it to a controller observer. In this sense, you aren't observing the model anymore, and it makes sense to make session and other controller type data information to that observer.
C) The simplest solution, with the least "structure", is to simply drop your logging code at the end of your action methods that you're watching.
Consider option #2 now, breaking MVC practices.
A) As you propose, you could find the means to getting your model Observer to have access to the Session data. You've coupled your model to your business logic.
B) Can't think of any others here :)
My personal inclination, without knowing anymore details about your project, is either 1A, if I want to attach people to records, or 1C if there are only a few places where I'm interested in doing this. If you are really wanting a robust logging solution for all your controllers and actions, you might consider 1B.
Having your model observer find session data is a bit "stinky", and would likely break if you tried to use your model in any other project/situation/context.
You're right about it breaking MVC. I would suggest using callbacks in your controllers, mostly because there are situations (like a model which save is called but fails validation) where you wouldn't want an observer logging anything.
I found a clean way to do what is suggested by the answer I picked.
http://pjkh.com/articles/2009/02/02/creating-an-audit-log-in-rails
This solution uses an AuditLog model as well as a TrackChanges module to add tracking functionality to any model. It still requires you to add a line to the controller when you update or create though.
In the past, when doing something like this, I have tended towards extending the User model class to include the idea of the 'current user'
Looking at the previous answers, I see suggestions to store the actual active record user in the session. This has several disadvantages.
It stores a possibly large object in the session database
It means that the copy of the user is 'cached' for all time (or until logout is forced). This means that any changes in status of this user will not be recognised until the user logs out and logs back in. This means for instance, that attempting to disable the user will await him logging off and back on. This is probably not the behaviour you want.
So that at the beginning of a request (in a filter) you take the user_id from the session and read the user, setting User.current_user.
Something like this...
class User
cattr_accessor :current_user
end
class Application
before_filter :retrieve_user
def retrieve_user
if session[:user_id].nil?
User.current_user = nil
else
User.current_user = User.find(session[:user_id])
end
end
end
From then on it should be trivial.
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails