rornoob.com

 


Recent Comments

RoRnoob said on 4-Sep-2012:
Thanks Michael. I was using rails 2.3.3 at the time and it doesn't look like :inverse_of was supported back then......
Michael said on 3-Sep-2012:
check out http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html...
dipnlik said on 9-May-2012:
Great idea, thanks or sharing :D

Getting memcached working with rails3 on Mac OS

Setting up rails 3 to use memcached is really simple using the dalli gem.

Add the dalli gem in your Gemfile

gem 'dalli'

Configure your production.rb (and development.rb, etc.) to use the dalli_store cache store

config.cache_store = :dalli_store, 'localhost:11211'

Install memcached

sudo port install memcached

Start memcached

memcached -vv

You should see something like:

slab class   1: chunk size    104 perslab 10082
slab class   2: chunk size    136 perslab  7710
...
slab class  38: chunk size 458992 perslab     2
<4 server listening

Great, you're ready to go.

Now start the rails console and test it out

rails c

What you want to see is:

> dc = Dalli::Client.new('127.0.0.1:11211')
=> #<Dalli::Client:0x00000104989248 @servers="127.0.0.1:11211", @options={:expires_in=>0}> 

> dc.set("test","something")
 => true

> dc.get("test")
 => "something" 

Great, it's working!

Not working?

What if you actually get:

> dc = Dalli::Client.new('127.0.0.1:11211')
=> #<Dalli::Client:0x00000104989248 @servers="127.0.0.1:11211", @options={:expires_in=>0}> 

> dc.set("test","something")
 => false

Check your memcached log output. Perhaps you see this?

<8 new client connection
<8 connection closed.

dalli on rails 3 has problems running with versions of memcached less than 1.4, so first check that you've got a recent version of memcached running.

telnet localhost 11211
version

If you get something like VERSION 1.2.8 then you have a problem... sudo port install memcached should have installed the newest version of memcached, so something in your mac configuration is calling up an old version of memcached that doesn't work with dalli.

One easy (but perhaps not ideal) solution is simply to add this line to your .bash_profile

alias memcached=/opt/local/bin/memcached

Then restart memcached from within a new command window and it should now be running the latest version.

0 comments | Add your comment

** in .gitignore

I started working on a new project using git for version control and eclipse RadRails for version control. Wanting to avoid commiting a whole lot of unnecessary files I looked around for an example .gitignore file for rails apps and came across the following:

log/*.log
tmp/**/*
tmp/*
doc/api
doc/app
coverage/*
public/javascripts/all.js
public/stylesheets/all.css
public/system
Gemfile.lock
.bundle

To that I added another entry to exclude the eclipse project file:

.project

So far so good, except that eclipse seemed to be creating a whole lot of extra temporary files that weren't being excluded by the settings above. E.g. things like:

app/views/example/.tmp_index.html.erb.55162~
app/views/example/.tmp_index.html.erb.77831~
app/views/example/.tmp_show.html.erb.88243~
app/views/example/.tmp_update.html.erb.56122~
app/views/example/more/.tmp__mockup.html.erb.7334~
app/views/example/more/.tmp_index.html.erb.97377~
app/views/example/more/.tmp_show.html.erb.52186~

No worries.., I'll just use that double asterix thingy to exclude all .tmp_ files from within my views:

app/views/**/.tmp_*

Nope - no good. The temp files are still there - that ** doesn't seem to be working as expected... One work-around is to list each folder with a */

*/.tmp*
*/*/.tmp*
*/*/*/.tmp*
etc.

That's not very nice. Why wont the ** work? From digging around on forums it seems there are plenty of other people who are also confused about ** in gitignore. Eventually though I stumbled upon an explanation: As soon as there is a / in the text then gitignore uses a different method of working out the paths. So, don't use / with **

**.tmp_*

That's better - the temp files are now ignored. It pays to be careful though - I'm now excluding .tmp_ from more than just my views. In this case that shouldn't be a problem but it's something to bare in mind.

0 comments | Add your comment

Sort it out - quick guide to ruby sort

Everyone knows how easy it is to sort an array in ruby right? Just use the sort method.

But what about when you need to sort an array of objects? Sort no longer works on the array as a whole - how would it know which attribute you want to sort by? Luckily, sorting arrays with objects and sorting on multiple attributes is still a breeze.

Let's say we have a collection of reports, where each report has a title, a price and a priority.

  reports = Reports.find(:all)

See how simple it is sort by any one or more of the report attributes!

simple sort

  cheapest_first = reports.sort { |a,b| a.price <=> b.price }

reverse sort

  dearest_first = reports.sort { |a,b| b.price <=> a.price }

sort by multiple attributes

  prioritised_reports = reports.sort { |a,b| [a.priority, b.price] <=> [b.priority, a.price] }

sort and overwrite the existing array

  reports.sort! { |a,b| a.price <=> b.price }

sort, while ignoring capitalisation

  reports.sort! { |a,b| a.title.downcase <=> b.title.downcase }

Of course ruby on rails also makes it easy to get your object collection pre-sorted by the database, that's probably faster in many cases but can't always be done (e.g. if you're working on an array of objects that's already had a bunch of filtering applied.)

rails db sort

  cheapest_three_reports = Reports.find(:all, :order=>:price, :limit=>3)

I'm sure that much more is possible, but the simple examples above show just how easy it is to sort through arrays in ruby on rails.

0 comments | Add your comment

Disappearing attributes... was I duped?

Showing a list of attributes that appear on either object A or it's parent object B should be really easy right?

I thought so, but then strange things started happening...

Here's the code:

  def get_full_list
    # start with attributes attached to this user
    all_attributes = self.attributes

	# build up an array of attribute codes
    attribute_codes = Array.new
    all_attributes.each do |a|
      attribute_codes << a.code
    end

    # add attributes to be inherited from the parent
	# (i.e. not attached at user level)
    parent_attributes = self.parent.attributes
    parent_attributes.each do |a|
      if attribute_codes.index(a.code).nil?
        attribute_codes << a.code
        all_attributes << a
      end
    end

    return all_attributes
  end

Now what's wrong with this code?

(Ok, there could be be plenty of things wrong with the code, but I want to focus on one point in particular. Feel free to add a comment below if you spot any other deficiencies).

The problem is that when I create a new attribute on the parent (from a web form not shown here) then the attribute magically migrates to the child object once the above code is executed - all I wanted to do was to get a list of all attributes attached to the child or parent, not to change any of the attributes at either level.

So why were the parent attributes magically moving over to the child? Especially since there is no save involved...

The answer to the main point (why did they move?) lies in this line:

    all_attributes = self.attributes

The solution is really, really simple, but it took me quite a while to work out what was really going on.

Rather than getting a copy of the array as I'd intended, I was actually getting the same array - i.e all_attributes became a reference to the real thing, so any changes to the elements in all_attributes were really changing elements in self.attributes.

So how can I get a copy of an array rather than a reference?

Just use the ruby core method dup. Simple as that.

.dup : "...produces a shallow copy of obj---the instance variables of obj are copied, but not the objects they reference..."

i.e. everything was solved once I changed the offending line to:

    all_attributes = self.attributes.dup

Now another problem, how can I justify to by boss the way I spent so much time implementing a 4-character fix???

Beware of comments in ruby 1.8.7

I can across a strange bug when upgrading to ruby enterprise 1.8.7 - one of my views displayed incorrectly after the upgrade but it was fine beforehand.

Here's an example of the code I'm referring to:

<table border="1">
<% @services.each do |service| %>
  <% next if service.status_id < 0 # skip inactive services %>
  <tr>
    <td><%= service.name %></td>
    <td><%= service.summary %></td>
  </tr>
<% end %>
</table>

Obviously I'm expecting a table that shows the name and summary of all active services.
i.e. something like this:

Email Notification User will receive email notification of events
Monthly Billing User account has been enabled for monthly billing
Free Reports User incurs no charge for viewing reports

That's exactly what I got under ruby 1.8.6, but look at the results when using ruby 1.8.7:

Email NotificationMonthly BillingFree Reports
User will receive email notification of events
User account has been enabled for monthly billing
User incurs no charge for viewing reports

Now why's that happening? The code is pretty basic so surely there's nothing that could go wrong.

Looking into the html source code I discovered that my code was generating this output:

<table border="1">


  Email Notification</td>
    <td>User will receive email notification of events</td>
  </tr>


  Monthly Billing</td>
    <td>User account has been enabled for monthly billing</td>
  </tr>


  Free Reports</td>
    <td>User incurs no charge for viewing reports</td>
  </tr>

</table>

i.e. note the missing <tr><td>

Strange...

Well it turns out that this line was the problem:

  <% next if service.status_id < 0 # skip inactive services %>

It's not the next or the if that causes things to go pear shaped - it's my comment! My comment has the effect of disabling the closing ruby tag so that nothing else is displayed until another closing tag is encountered, i.e. the one after displying the service name: <%= service.name %></td>

Strange that the same code worked fine using ruby 1.8.6, but at least the workaround is easy - just remove comments from the end of code lines, like so:

<table border="1">
<% @services.each do |service| %>
  <% next if service.status_id < 0 %>
  <tr>
    <td><%= service.name %></td>
    <td><%= service.summary %></td>
  </tr>
<% end %>
</table>
0 comments | Add your comment

Rails flash[] messages not clearing

I'm sure you're aware of flash[:notice] - that friendly way to pass messages on to your views, even when an HTTP redirect is involved.

I was happily using flash[:notice] to display a Changes Saved message to users when they successfully updated their profile but after making some additional changes I began encountering a perplexing problem - sometimes the flash messages would "stick around" for a while - i.e. they weren't always clearing themselves out automatically.

After some digging around it seems the answer is a simple one. In fact you probably already know it. (Well maybe not if you've read this far...)

The answer comes from the ruby on rails help pages themselves:

"When you need to pass an object to the next action, you use the standard flash assign ([]=). When you need to pass an object to the current action, you use now, and your object will vanish when the current action is done."

Ok, that explains it. My old code was using flash[...] in combination with redirect_to but my new code was using it with render. i.e. the messages weren't being cleared out properly because rails didn't consider my renders to be new pages views.

So to summarise, use:

  • flash[:notice] with redirects (e.g. redirect_to :action => 'something')
  • flash.now[:notice] with renders (i.e. render :action => 'something')

Result: No more flash clearing issues.

Creating nested objects with validation of parent id

Nested attributes in rails 2.3.3 can really simplify the updating of nested models, but creating nested models throws up an interesting problem when your model uses validates_presence_of... at least it does for me...

Lets say I have two models, a User and an Account. The User accepts nested attributes for the Account, while the Account requires the presence of a user_id.

E.g.

class User < ActiveRecord::Base
  has_many :accounts
  accepts_nested_attributes_for :accounts
  ...
end

class Account < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :user_id
  ...
end

Now when I go to create a new user and account, up pops a validation error:

ActiveRecord::RecordInvalid: Validation failed: Accounts user can't be blank

It doesn't take much to work out that the error is due to the account object being saved before the user object - i.e. the user doesn't yet exist in the database and so doesn't have a user_id. This causes the validates_presence_of :user_id clause in the account model to fail.

I thought about simply removing the check for user_id but my user and account objects are part of a legacy system - i.e. they're used in other places and I don't want to risk removing validations that are probably required.

So what to do?

The solution I'm going with is to conditionally validate the presence of the user_id - i.e. require the validation only if the account object is being created as part of a dependent creation of a user object.

I've modified my code to set an attribute :new_user on the account when creating a new user with nested account, then check for this attribute as follows:

class Account < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :user_id, :unless => :new_user
  attr_accessor :new_user
  ...
end

That seems to do the trick - the validation message disappears and I'm able to create new users with embedded accounts.

Hopefully this issue will be fixed in a future version of rails. Until then... does anyone have a better solution I could use?

rspec route_for tests failing

Don't you hate it when your tests work on one server but not on another?

That's what happened to me today... rake spec on my development machine gives me a nice 0 failures, but run the same tests on the test server I get a frustrating 16 failures

So what's gone wrong? On the test server I'm getting error messags like these:

Name: UsersController route generation should map { :controller => 'users', :action => 'destroy', :id => 1} to /users/1

Type: Error

Message: Test::Unit::AssertionFailedError in 'UsersController route generation should map { :controller => 'users', :action => 'destroy', :id => 1} to /users/1'

The recognized options <{"action"=>"show", "id"=>"1", "controller"=>"users"}> did not match <{"action"=>"destroy", "id"=>"1", "controller"=>"users"}>, difference: <{"action"=>"destroy"}>

Why does that happen? The code is exactly the same. The configuration files are exactly the same. This problem is frustrating in the least!

After some digging around I found out that the problem was due to newer version of rspec on test server - version 1.2.8 compared with 1.1.12 on the development machine.

It would seem that the newer rspec is stricter (better) than the earlier version. The route_for mthod now requires the HTTP method to be specified if it isn't a GET.

So the solution? Fix up the test specifications to the stricter requirements.

e.g. change

  route_for(:controller => "users", :action => "destroy", :id => "1").should == "/users/1"

to

  route_for(:controller => "users", :action => "destroy", :id => "1").should == {:path => "/users/1", :method => :delete}

Run the tests again...

0 failures

That's better!