Passing current user id to Rails models

Long time since last post, isn’t it? Well, I’m completely busy with my work in Nettigo, selling Arduinos and other nice gears :) I do not do gigs anymore, so there is a lot less reasons to write about Rails.

But I still use this framework, since my backend software is written in Rails, so here is one thing which I think can be useful for You.

Often we want to pass ID of current user to Rails model internals. Most answers are don’t do that, this is controller work to do authorization stuff, MVC is to prevent such thins, etc.

here was not related picture (as usual on this blog). Some readers were complaining it was not only unrelated but even offending. Well, maybe they were right, so, I have removed that picture.

Yeah, right, but what if we want to keep some kind of audit trail what is happening with given model? This is not authorization, just who done what.

Since in my application case audit records are created in observers simple passing user id as some additional parameter is no go for me – I don’t want to change interface just to pass user info, that breaks too much things.

So, I have chosen this approach:

def with_user user, &block
  if user.blank?
    yield
  else
    Thread.current[:user] = user.id
    yield
    Thread.current[:user] = nil
  end
end

And each operation I want to track I invoke:

with_user(current_user) do
 model.do_some_stuff
end

Thread.current acts as a hash accessible in current thread and allow us to pass some info skipping MVC isolation (You wanted that, right? :) )

In observers I can access user ID and store it with record. If it is not present, audit trails is being marked as created by System. That means probably cron job, or console action (well on console I can run code inside with_user block when needed).

This is safe as long each request is being processed in single thread and we make sure that user ID will be cleared after our operation. When ID will stay in Thread.current, next request being processed by this Rails app instance will have access to that value and wrong audit records will be created.

Code has to be updated, since any exception will occur inside block, code after yield will be skipped and user ID won’t be erased from Thread.current. Here it is, final version:

def with_user user, &block
  if user.blank?
    yield
  else
    Thread.current[:user] = user.id

    begin
      yield
    ensure
      Thread.current[:user] = nil
    end
  end
end

Code inside ensure will be executed each time – both when exception was raised and when execution was clean.

Extracting fixtures

I’m still using fixtures. Shame, I know.

Why I do use them instead of Factory Girl or other solution like that? Well, fixtures can be much more closer to real data than mocks from Factory. How come, You ask? Fixtures are imaginary data exactly like mocks from other sources!

My answer is: that depends how You create fixtures. If You create them by hand, indeed they are disconnected from real world (like all mocks).

What is Your real data/mocks ratio?

What is Your real data/mocks ratio? CC by hsing

But I prefer to extract fixtures from real (production) database. That way I can easily pick entries created by users which are edge cases. The only trouble is with creating fixtures. For some time I’m using modified extract_fixtures rake task. I have added some conditions to extracting process – SQL condition to select only particular records and adjusted syntax to recent rake.

This is useful especially when You are about to take over application code which has no tests. Extracting real data is quick way to start write integration tests (in such case they have are most efficient – time invested and application code coverage).

How to extract fixtures without pain?

Continue reading

Associations and to_json on collections of objects

ActiveRecord’s Serialization module does provide to_json method. It allows include associations when dumping to JSON, but it works only for single objects. If You want to run it against collection, returned by find :all You will get:

>> Model.all.to_json :include =>:association
TypeError: wrong argument type Hash (expected Data)
	from (irb):13:in `to_json'
	from (irb):13

If You want to get in JSON collection with some associations You have to do it manually:

    @companies = Company.all, :include => :address
    render :text => "[ %s ]" % @companies.collect{ |c|
      c.to_json(:include => :address)
    }.join(","),
      :content_type => 'application/json'

MySQL, collations and Rails – or server, database, connection – that is not all

Last few weeks were somewhat crazy time. But I’m seeing finish line not so far away and I hope to find more time for blogging in upcoming weeks. At least I have few things to share, which could be interesting for other Rails developers.

So let’s start with MySQL and it’s collations. I have wrote about them few months ago. Few days ago, I had finally installed Linux on my notebook (last two months I was running Linux from USB drive). And it seems that there were some minor changes in my environment. I was working on CARtoteka, and when I was running migrations they were stopped in half with error like :

Mysql::Error: Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and
(utf8_general_ci,COERCIBLE) for operation '=': 
SELECT count(DISTINCT  companies`.id) AS count_all FROM `companies`  
LEFT OUTER JOIN `addresses` ON  addresses.company_id = companies.id 
WHERE (city = 'Pozna?' and moderated)

What the heck? I have doublechecked I have set connection collation and other – utf_general_ci as I could expect.

After some digging I have found what was a cause.

Continue reading