RailsCasts Pro episodes are now free!

Learn more or hide this

Recent Comments

Avatar

There's even a Railscast about it, though quite old, but still applies http://railscasts.com/episodes/29-group-by-month

Avatar

Yeah redis is incredibly fast and fits pretty well in storing non critical data like user activities. I have already read some of the documentation and played a bit with it in development but haven't yet used it in a production environment. Could you recommend some resources where i can learn more about Redis? Which path did you follow?

Avatar

Hi - Does anyone know how to group the output of the activity list by date in the view? So it looks something like:

Thursday, February 14, 2013
     -  13:40: Sample activity
     -  13:30: Sample activity
     -  13:20: Sample activity

Tuesday, February 12, 2013
     -  15:40: Sample activity
     -  14:30: Sample activity

Thanks!

Avatar

Has anyone had an issue where the popup window that opens doesn't close after it finished authenticating? Everything else is working properly but the window does not close after its done.

Avatar

This is discussed in the project's github issues, where a user even posts a link to a workaround until this discussion settles on a solution.

Avatar

I've done this using Soulmate before. I added the after_save for the models I wanted to be able to search for. In my data I'd store the search term as the key, and in the value i'd store a hash containing the full name, id, and type. Worked great.

Avatar

1) Not be amiss to add csrf token in your backbone forms and turn it on in application controller:

ruby
def handle_unverified_request
  #TODO add here code to empty the session
  raise ActionController::InvalidAuthenticityToken
end
ruby
<input name="authenticity_token" type="hidden" value="<%= $('meta[name="csrf-token"]').attr('content') %>">

That works fine but I doubt that it is really necessary because browsers have ajax crossdomain restrictions anyway.

I am right or not?

2) I just found it handful when you have multiple fields in your forms use backbone-syphon-rails gem. It doing form serialisation for you and you can write something like that:

coffeescript
createNote: (event) ->
    event.preventDefault()
    data = Backbone.Syphon.serialize(this)
    @collection.create data
Avatar

Interesting, as from watching this video it looked like the trackable objects had to be loaded.

The reason to use DJ is if you're writing the activity from a model/observer callback, to defer a few additional SQL calls required to populate the denormalized data. If you have everything you need at the time of writing, its unnecessary.

Avatar

Yep, using activerecord/sql-db will definitely be an issue. I wonder whether Ryan is planning on doing a "from scratch" implementation using redis. I will check out your gem.

Avatar

hey Ryan, please make a new screencast about that.

Avatar

That's exactly what public_activity does, minus delaying. Also using delayed_job makes no sense, because it still has writes to the db, so you might as well write the activity. Better option is something redis based.

Avatar

Good idea about delaying the activity creation!

Avatar

Hi @iashraf. I'll prepare a wiki page for this and post the link here :)

Avatar

Yes, this is ugly, but completely optional. You can do creation manually using #create_activity and only use #tracked to set defaults, if for example you know you will always need to cache recipe's name.

Avatar

meta_request appears to conflict with exception_notification as featured in a previous episode. I'm running 1.8.7 so am unable to use better_errors.

I'm getting this:

uninitialized constant ActionDispatch::ExceptionWrapper
meta_request (0.2.1) lib/meta_request/middlewares/app_request_handler.rb:13:in `call'

Does anyone know why?

Avatar

I had to so something like that too in my own app.

The simplest solution was to unload all the publishing of activities into a background job and cache the view of all activities. App response is around 150-200ms so I am happy. Activities are generally published within minutes so that is good enough.

I don't really like when a solution/gem outgrows the problem in complexity, plus callbacks are evil.

PS: I don't want to know how complex this would get if you also want comments on an activity to be published on the 'activity item'. So comment on 'A commented on picture of B' -> creates comment on picture of B, which is what I had to do in my app.

Avatar

+1 This is a good idea :) Thanks for your sharing.

Avatar

Javi, thanks! Had to figure it our myself, only to discover later that you have this snippet here for over 2 years. I went a bit further with optimization, so my code is as follows:

ruby
config.before(:suite) do
  # Do truncation once per suite to vacuum for Postgres
  DatabaseCleaner.clean_with :truncation
  # Normally do transactions-based cleanup
  DatabaseCleaner.strategy = :transaction
end

config.around(:each) do |spec|
  if spec.metadata[:js]
    # JS => run with Poltergeist/Selenium that doesn't share connections 
    # => can't use transactions
    # deletion is often faster than truncation on Postgres - doesn't vacuum
    # no need to 'start', clean_with is sufficient for deletion
    spec.run
    DatabaseCleaner.clean_with :deletion
  else
    # No JS => run with Rack::Test => transactions are ok
    DatabaseCleaner.start
    spec.run
    DatabaseCleaner.clean
  end
end
Avatar

Would it be possible to have the progress bar appear in the div where the image will be appended? Something like this: http://dribbble.com/shots/919309-Uploader-Interaction-and-Animation

Avatar

In some cases, you can help the performance by pushing the assets to a different database. We have to keep detailed logs on a web application to track back changes made and can do this through having a beefier database server to handle the queries of the live logs.

ruby
log_database_producion:
  adapter: mysql
  host: other_host
  username: logmein
  password: supersecret
  database: logs

Then in your special model:

ruby
class AccessLog < ActiveRecord::Base
  establish_connection "log_database_#{Rails.env}"
end

To keep those pesky credentials from being in your application code.

If you want to reuse this connection in multiple models, you should create a new abstract class and inherit from it, because connections are tightly coupled to classes (as explained here, here, and here), and new connections will be created for each class.

If that is the case, set things up like so:

ruby
class LogDatabase < ActiveRecord::Base
  self.abstract_class = true
  establish_connection "log_database_#{Rails.env}"
end

class AccessLog < LogDatabase
end

class CheckoutLog < LogDatabase
end
Avatar

FYI: If you look in the source, Capistrano::CLI.ui just links to a Highline object. So you can do Capistrano::CLI.ui.ask("Enter something") instead of CLI.password_prompt :)

Avatar

If you check cat /etc/sudoers you might find that the "admin" group is there. In that case just run groupadd admin and continue as usual.

Avatar

Is there a way to set a column in the Activities table to attr_accessible? (no model?) I've added a custom field to query against but need it to be accessible? - Thanks!

Avatar

I was just about to ask Ryan if he could do a cast on how to achieve this from scratch... with something like Redis :)

Could you write a blog post about implementing a simple activity tracker from scratch Jon? Interested to see how you approach it.

Avatar

And, how do you $inject the dependencies to be able to minified your angular.js?

According to the doc you have to

MainCtrl.$inject($scope, $http)

for it to properly minify your js assets in production.

Avatar

I love the idea of using redis as the backend too!

Avatar

A starting question would be, where do I find good read for the kind of question I have here?

I'm using the 'through' approach but I'm stumbling over some basics.
The models are set up correctly, but in my show.html.erb
I want to check for the existence of the other object before I show a value from it.
I'm running into 'undefined method' when I
<% if product.category %> <-- error
<% product.category.each do |pc| ...

Do I need to add methods to my categorization model, or some assignments in my controller for one of the models?

Avatar

If you're using the haml-rails gem, for the moment you may get an error running generators, something like:

(erb):19:in template': undefined methodkey_value' for #Haml::Generators::ScaffoldGenerator:0x007fb9712be1e8 (NoMethodError)

This error is fixed by a recent pull request.

Update your Gemfile to use the master branch for the moment...

ruby
gem 'haml-rails', git: 'https://github.com/indirect/haml-rails'
Avatar

I ran in to problems with the rbenv install on Ubuntu 12.04. I changed the bootstrap line to version 12-04 and it still didn't work. I fixed it like this:

rbenv.rb (remove line 2)
set_default :rbenv_bootstrap, "bootstrap-ubuntu-10-04"
rbenv.rb (remove line 20)
run "rbenv #{rbenv_bootstrap}"
base.rb (update line 14)
run "#{sudo} apt-get -y install python-software-properties build-essential"
Avatar

+1 on that refactoring at 9:00, seems essential. I cringed seeing "controller" in that model, too!

Avatar

Interesting timing, as I've been working on a couple different approaches for activity feeds this month. My current process goes like this:

  • Queue creation of activity record via a delayed process (DJ, etc)
  • In an Activity model before_create callback, retrieve the relevant objects, store references to them, but also denormalize the relevant bits (ex. post name, commenter name) into an hstore hash.

By doing the latter, there are no lookups required for the trackable objects when generating your activity feed. Its super fast. Activity streams can be generated purely with the denormalized data, but we have the trackable references in case we need them. Also, you don't lose the data (post name, etc) when an object is deleted, and can instead defer handling of clickthroughs to deleted objects.

Avatar

Create a new public activity in the show action?

Avatar

Is there a way to track views? e.g., steve viewed ryan's lasagne recipe.

Avatar

Thanks a lot. I wasted a couple of hours doing all sorts of things around this.

Avatar

Exactly, that is the main reason params exists, to cache important attributes of associated objects. See http://rubydoc.info/gems/public_activity/PublicActivity/Tracked/ClassMethods#tracked-instance_method

Avatar

It's at least going to involve a select on every corresponding table included in the activities, there's really no way around that.

That's why I suggest saving an arbitrary hash of attributes on the activity object itself. 9 times out of 10 you only want, say, the ID and Name of the associated object in order to generate a link to it.

Avatar

No sweat, let me know if I can help!

Avatar

All associations are polymorphic, so it is a hard topic. I personally cache whole pages of activities.

Avatar

How does this handle fetching the associated models? This feels like an n+1 query to me, no?

Avatar

Looks like the gem provides a lot of features out of the box but I think for any serious social network app, activity feeds are quite important so my personal preference is to build this something from scratch and that is what we have done in MeroCampus.com
But for simpler app it is the way to go. Thanks.

Avatar

You make a good point about performance and storage. Redis is a good idea as backend - which I will look into.

Avatar

Used similar gem for my app called 'timeline_fu'. In rails 3.2 found working only this one - github.com/crftr/timeline_fu.git

Avatar

Public_activity looks like a great gem but we had some trouble with this approach in production. When tracking a number of changes on a number of models it bumped writes to the database quite substantially. Then when drawing back the associated objects on multiple activities there's a performance hit again.

We build our own solution for this called Redcrumbs.

It works in a very similar way but uses Redis as a backend. The benefits include superfast reads and writes but also Redis has an option to simply expire old keys, similar to memcached.

It also allows common attributes to be cached on the activity object, so that in 90% of cases the associated object doesn't have to be drawn from the primary database.

We've been using it in production to provide news feeds in our games for at least 6 months without a hitch.

Public_activity looks great for smaller sites though and there's an argument for keeping everything in the main database and on the same database structure.

Avatar

Great episode thank you but I have a question about listing activities. Why are some activities duplicated like update?

Avatar

This approach doesn't seem to work on all mobile browsers. It worked in Chrome for iPhone, but not in Safari.

Avatar

delicious....... yes I believe that is the correct adjetive for this episode.