Let's say I had an app that was an address book. I'd like to have a page dedicated to a "dashboard". On this page, I'd like to have a running list of the events that happen within the app itself.
Event examples could be:
A user adds a contact.
A user deletes a contact.
A user updates a contact.
What would be the best way to create this type of functionality? Originally I felt that I could do some creative database calls with existing data, but I wouldn't be able to deal with events that deleted data, like when a contact is deleted.
So now I'm thinking it would have to be a separate table that simply stored the events as they occurred. Would this be how most sites accomplish this?
I could go throughout my app, and each time a CRUD operation is performed I could create a new item in the table detailing what happened, but that doesn't seem very DRY.
I supposed my question would be - what's the best way to create the dashboard functionality within an already existing application such as an address book?
Any guidance would be greatly appreciated.
The easiest way to do this is to user Observers in addition to a "logger" table in your database.
Logger
id
model_name
model_id
message
This way you can set up an Observer for all models that you want to log, and do something like this:
after_delete(contact)
Logger.create({:model_name => contact.class.to_s,
:model_id => contact.id,
:message => "Contact was deleted at #{Time.now}"})
end
Now you can log any event in a way you deem fit. Another great addition to this kind of structure is to implement "Logical Deletes", which means you never really delete a record from the table, you simple give it a flag so that it no longer shows up in regular result sets. There's a plugin that does this called acts_as_paranoid.
If you implement both things above, the dashboard can log all important actions, and if you ever need to see what happened or view the data of those events, it's all in the system and can be accessed via the Console (or controllers, if you set them up).
You may want to check out Timeline Fu: http://github.com/jamesgolick/timeline_fu:
class Post < ActiveRecord::Base
belongs_to :author, :class_name => 'Person'
fires :new_post, :on => :create,
:actor => :author
end
I've created similar functionality in the past using acts_as_audited.
This helps you track changes to your models, which you can then present to the user.
It basically just tracks the events in a separate table, as you suggested.
You can use Observers in order to handle the events.
Then just store event with the information needed in the database from those Observers.
Here is a quick link to get you started.
user paper_trail plugin, it is awesome!. We modified it though, it is used for all our audit system for complicated release process.
Related
I am trying to create a Rails app and I have a database consisting of author and a quotation by that author.
Now different users can choose to destroy or kill quotations from the database however it must only be deleted for that particular user i.e other users should still be able to see quotes that they didn't delete even if another user did.
I know that I would need to implement cookies but other than that I am unsure how to proceed. Can anyone point me to a tutorial or give me some pointers to get started on this complex task?
You surely have a User model in your application - one 'Rails-like' way to go about this would be to add a has_and_belongs_to_many relationship between User and Quotation.
This creates a relationship between each individual user and 'their' quotations. This relationship can be deleted without actually deleting a quotation, so all quotations would still be available to other users. If you want each user to be able to see all quotations by default, you would need to set up the relationship in advance.
Assuming you are using Devise to log your users in, all you'd need to do then is to replace Quotation.all with current_user.quotations in whichever controller you are using to display quotations.
The Rails guide linked above is quite helpful but basically you just need to add something like the following:
class User
has_and_belongs_to_many :quotations
before_create :add_quotations
def add_quotations
self.quotations << Quotation.all
end
#etc...
end
class Quotation
has_and_belongs_to_many :users
#etc...
end
and then run a migration adding a new table called users_quotations with the columns user_id and quotation_id.
EDIT
As #Yule pointed out this wouldn't let users see any quotations that were created after they were, and it would be quite annoying to have to set up the join tables in advance, so a more efficient way would be to have an excluded_quotations join table instead. So users can see all quotations except the ones that they have excluded.
long time reader first time poster.
I recently started using ruby on rails so I am still very new to the environment (even though I have completed a few guides) so be gentle please.
What I want to do is create a sort of archive table of another table that the user can access at any time(via a different link on the website).
So for example, if I have the "users" table, I want to be able to archive old users but still give the option for someone to go and view them.
Basically, it will sort of have to delete the user from the initial table, and save his/her info in to the archived_users table.
Thank you for your time.
I figured my comment was more of an answer, so posting it here and adding more info
In this situation you're better off adding some sort if "active" flag to the users table, which you can flip on or off as needed. That way you don't need to worry about dealing with yet another model class, and you can reuse all the same view and controller structures. In your views, you can then simply "hide" any inactive users (and maybe only show inactive folks if the logged in user is an admin...etc).
You also have the freedom to include other meta data such as "deactivated on" (time stamp) for example.
Long story short, if you're concerned about performance, with proper indexing (and partitioning if necessary), you shouldn't really need to create a separate archive table.
The only reason I can think of to do this is if you're dealing with billions upon billions of records, and/or growing by an insane amount (which is probably not your case).
The best way to do this is probably to add a column called deleted on the original Users table. You can then filter out the old users in normal circumstances (preferably using a default scope) but allow them to be seen/queried when needed.
Papertrail might work for you.
It creates a "versions" table and logs create/update/destroy events for any class which includes has_paper_trail. For example:
class User < ActiveRecord::Base
has_paper_trail
end
deleted_users = Papertrail::Version.where(item_type: User, event: "destroy")
deleted_users.last.reify.name # assuming the users table has a 'name' column
I have a requirement that certain attribute changes to records are not reflected in the user interface until those changes are approved. Further, if a change is made to an approved record, the user will be presented with the record as it exists before approval.
My first try...
was to go to a versioning plugin such as paper_trail, acts_as_audited, etc. and add an approved attribute to their version model. Doing so would not only give me the ability to 'rollback' through versions of the record, but also SHOULD allow me to differentiate between whether a version has been approved or not.
I have been working down this train of thought for awhile now, and the problem I keep running into is on the user side. That is, how do I query for a collection of approved records? I could (and tried) writing some helper methods that get a collection of records, and then loop over them to find an "approved" version of the record. My primary gripe with this is how quickly the number of database hits can grow. My next attempt was to do something as follows:
Version.
where(:item_type => MyModel.name, :approved => true).
group(:item_type).collect do |v|
# like the 'reify' method of paper_trail
v.some_method_that_converts_the_version_to_a_record
end
So assuming that the some_method... call doesn't hit the database, we kind of end up with the data we're interested in. The main problem I ran into with this method is I can't use this "finder" as a scope. That is, I can't append additional scopes to this lookup to narrow my results further. For example, my records may also have a cool scope that only shows records where :cool => true. Ideally, I would want to look up my records as MyModel.approved.cool, but here I guess I would have to get my collection of approved models and then loop over them for cool ones would would result in the very least in having a bunch of records initialized in memory for no reason.
My next try...
involved creating a special type of "pending record" that basically help "potential" changes to a record. So on the user end you would lookup whatever you wanted as you normally would. Whenever a pending record is apply!(ed) it would simply makes those changes to the actual record, and alls well... Except about 30 minutes into it I realize that it all breaks down if an "admin" wishes to go back and contribute more to his change before approving it. I guess my only option would be either to:
Force the admin to approve all changes before making additional ones (that won't go over well... nor should it).
Try to read the changes out of the "pending record" model and apply them to the existing record without saving. Something about this idea just doesn't quite sound "right".
I would love someone's input on this issue. I have been wrestling with it for some time, and I just can't seem to find the way that feels right. I like to live by the "if its hard to get your head around it, you're probably doing it wrong" mantra.
And this is kicking my tail...
How about, create an association:
class MyModel < AR::Base
belongs_to :my_model
has_one :new_version, :class_name => MyModel
# ...
end
When an edit is made, you basically clone the existing object to a new one. Associate the existing object and the new one, and set a has_edits attribute on the existing object, the pending_approval attribute on the new one.
How you treat the objects once the admin approves it depends on whether you have other associations that depend on the id of the original model.
In any case, you can reduce your queries to:
objects_pending_edits = MyModel.where("has_edits = true").all
then with any given one, you can access the new edits with obj.new_version. If you're really wanting to reduce database traffic, eager-load that association.
Hey,
I'm trying to implement a message system as used in social networks. The problem I'm facing is first about the database structure and second how to implement it in rails.
My first idea is I'm using 3 tables:
messages: id|subject|text|created_at
receivers: id|message_id|read:boolean
creators: id|message_id|read:boolean
now I'm wondering how to implement following features:
1.) a user can delete his message. but as both want to read the message, how to make sure the message is only deleted when both users have deleted it.
2.) how do I implement a reply? or actually how do I find the corresponding creator?
3.) how to find out whether a mail was read by the receiver?
another idea is:
creator_messages: id|creator_id|receiver_id|subject|text|read|created_at
receiver_messages: same as creator_messages
this distinguishes between the users, so they can delete individually their messages. but how do i find out, whether the mail was read or not?
my third approach was basicly my second but only one table messages and then displaying this to the user.
1. the message is deleted as soon as one of the user deletes it.
2. actually how do I represent the relationships as has_many and belongs to?
I thought it would work like this:
model User
:has_many :send_messages, :class_name=>"messages", :foreign_key=>"creator_id"
:has_many :received_messages, :class_name=>"messages", :foreign_key=>"receiver_id"
end
model Messages
belongs_to :user
end
but somehow I didn't get it to work. guess I'm missing something basic here.
Hope someone can help me =) thanks a lot
ok, if i understand it correctly the messages would have maximum of 1 receiver and 1 sender.
In that case i would do the following:
I would create just a messages model, this would have the extra fields
- receiver_read
- receiver_deleted
- sender_deleted
Now you can add hooks to the model like "after_save", "after_create" in here you can check if the receiver_read has just been set to true via for example with the receiver_read_changed? method, if this is true you can notify the sender or do something else with it.
With this after_save hook you can also check that if the sender_deleted is just set to true and the receiver_deleted is already true you delete the whole message.
When you have multiple receivers I would create a join model for the receivers and have the sender_deleted and sender_id in the message model.
In the join model i would add the columns receiver_id, read and deleted.
Now i would use the before_save method on the messages and the join models to check if the message needs to be deleted or if the sender has to be notified of the message that has been read.
The solution might be:
1) I would create a deleted(_at) flag in the receivers table as well, if you really want to hard-remove the messages from the database if all of the receivers deleted it you could setup a cronjob or something.
2) have a creator_id in the messages model instead of a creators table, i mean how can multiple people create the same message?
3) I don't really get this one, i guess you set the "read" flag on the receivers table to "true" when that user opens up the message, after this you can make a scope in the users model like "scope :read, where(:read, true)" and also a scope unread.
I hope this is what you mean.
We are creating a system in Ruby on Rails and we want to be able to offer our users a bit of control about notifications and actions that can take place when some pre-defined trigger occurs. In addition, we plan on iterating through imported data and allowing our users to configure some actions and triggers based on that data.
Let me give you a few examples to better clarify:
Trigger - Action
------------------------------------------------------------------------
New Ticket is Created - User receives an e-mail
New Ticket Parsed for Keyword 'evil' - Ticket gets auto-assigned to a
particular group
User Missed 3 Meetings - A ticket is automatically created
Ideally, we would like some of the triggers to be configurable. For instance, the last example would possibly let you configure how many meetings were missed before the action took place.
I was wondering what patterns might help me in doing this event/callback situation in Ruby on Rails. Also, the triggers and actions may be configurable, but they will be predefined; so, should they be hard coded or stored in the database?
Any thoughts would be greatly appreciated. Thanks!
Update 1: After looking at it, I noticed that the badges system on SO is somewhat similar, based on these criteria, I want to do this action. It's slightly different, but I want to be able to easily add new criteria and actions and present them to the users. Any thoughts relating to this?
I think that what you are looking for are the Observers.
In your examples the Observers could handle the first and the third example (but not the second one, since an Observer only observes the object, not interact with it, even though it is technically possible).
Some code to show how I mean:
class TicketObserver < ActiveRecord::Observer
def after_create(ticket)
UserMailer.deliver_new_ticket_notification
end
end
class UserObserver < ActiveRecord::Observer
def after_update(user)
Ticket.new if user.recently_missed_a_meeting and user.missed_meetings > 3
end
end
And then add the observers to environment.rb
config.active_record.observers = :user_observer, :ticket_observer
Of course you will have to fill in the logic for the missed_meetings, but one detail to mention.
Since the after_update will trigger after every time that the user is being updated, the recently_missed_a_meeting attribute is useful. I usually follow the thinking of restful-authentication and have an instance variable that is being set to true everytime I want to trigger that row. That can be done in a callback or in some custom logic depends on how you track the meetings.
And for the second example, I would put it in a before_update callback, perhaps having the keywords in a lookup table to let users update which words that should trigger the move to a specific group.
You should look at the "callback" methods in Rails
For docs see - Callbacks
Your first rule would be implemented via the after_create method.
If you want them to be configurable, I would suggest using a model / table to store the possible actions and doing a lookup within the callback.
If this is high volume, be sure to consider caching the configuration since it would end up doing a db lookup on each callback.
Maybe something like a state-machine can help. Try AASM gem for RoR.