Best ruby-on-rails-3 questions in January 2012

Scope vs Class Method in Rails 3

9 votes

Based on the Rails 3 API, the difference between a scope and a class method is almost non-existent.

class Shipment < ActiveRecord::Base
  def self.unshipped
    where(:shipped => false)
  end
end

is the same as

scope :unshipped, where(:shipped => false)

However, I'm finding that I'm sometimes getting different results using them.

While they both generate the same, correct SQL query, the scope doesn't always seem to return the correct values when called. It looks like this problem only occurs when its called the same way twice, albeit on a different shipment, in the method. The second time it's called, when using scope it returns the same thing it did the first time. Whereas if I use the class method it works correctly.

Is there some sort of query caching that occurs when using scope?

Edit:

order.line_items.unshipped

The line above is how the scope is being called. Orders have many line_items.

The generate_multiple_shipments method is being called twice because the test creates an order and generates the shipments to see how many there are. It then makes a change to the order and regenerates the shipments. However, group_by_ship_date returns the same results it did from the first iteration of the order.

def generate_multiple_shipments(order)
  line_items_by_date = group_by_ship_date(order.line_items.unshipped)

  line_items_by_date.keys.sort.map do |date|
    shipment = clone_from_order(order)
    shipment.ship_date = date
    line_items_by_date[date].each { |line_item| shipment.line_items << line_item }
    shipment
  end
end

def group_by_ship_date(line_items)    
  hash = {}
  line_items.each do |line_item|
    hash[line_item.ship_date] ||= []
    hash[line_item.ship_date] << line_item
  end
  hash
end

I think your invocation is incorrect. You should add so-called query method to execute the scope, such as all, first, last, i.e.:

order.line_items.unshipped.all

I've observed some inconsistencies, especially in rspec, that are avoided by adding the query method.

You didn't post your test code, so it's hard to say precisely, but my exeprience has been that after you modify associated records, you have to force a reload, as the query cache isn't always smart enough to detect a change. By passing true to the association, you can force the association to reload and the query to re-run:

order.line_items(true).unshipped.all

How to write negative loop in ruby like for(i=index; i >= 0; i --)

8 votes

I am new with ruby. wish to know how to write following loop in ruby.

var index=25; 

for (i = index; i >= 0; i--) { 
    print i;
}

Thanks in advance

There are many ways to perform a decrementing loop in Ruby:

First way:

for i in (10).downto(0)
   puts i 
end

Second way:

index = 10 # any value
index.downto(0) do |i|
  puts i
end

Third way:

i = index
until i > 0
  i -= 1
  puts i
end

How to handle translations for an ActiveModel?

6 votes

I am using Rails 3.1.1 and I would like to properly translate error messages for an ActiveModel. I don't know if overwriting the i18n_scope is the right way to solve my problem (or if there are other ways), but the official documentation says:

i18n_scope()

Returns the i18n_scope for the class. Overwrite if you want custom lookup.

... how should I overwtite the i18n_scope?

At this time I am getting a the following "alert":

# Note the 'activemodel' part
translation missing: de.activemodel.errors.models.my_class.attributes.message.blank

# I would like to "map" translations to 'de.activerecord.errors.messages.blank'
# as made for all other ActiveRecord classes in my application

My ActiveModel class is like the following:

class MyClass
  include ActiveModel::Conversion
  include ActiveModel::Validations
  include ActiveModel::Dirty
  extend  ActiveModel::Naming
  extend  ActiveModel::Translation

  validates :name, :presence => true

  ...
end

It should be a class method, by analogy with AR code:

class MyClass
  include ActiveModel ...
  class << self
    def i18n_scope
      :activerecord
    end
  end
end

When should I use development vs testing group in gemfile for testing gems?

6 votes

For some reason this hasn't hit home, and I'm wondering if someone could help explain..

I noticed when installing 'guard' gem, that they recommend placing a lot of the gems in the 'development group' in the gemfile, such as 'growl' and 'rb-notifu': https://github.com/guard/guard..

Also Ryan Bates seems in one screen cast seems to put many of these in 'development' & 'testing': http://railscasts.com/episodes/264-guard?view=asciicast

But in another puts it all in 'testing': http://railscasts.com/episodes/275-how-i-test

It'd be nice to understand this so I don't have to reference tutorials all the time. Thanks!

Gems that you run from the development environment should be present in both the development and test groups. You run things like rspec cucumber and guard from development and they run in the test environment, you need them in development to run the rake tasks and the executables.

Gems that only run while in test mode, such as capybara email_spec and launchy can exist only in the test group and still function correctly.

I hope this helps clear things up.

As a general rule, gems that are executable need to be in both. Also, if you are unsure, put it in both groups too.

Edit

If the gem you are using has generators (rails generate), it needs to be present in both test and development.

Rails validatation to ensure a username does not clash with an existing route?

5 votes

I want to ensure users can't create usernames that clash with my existing routes. I would also like the ability to deny future routes I may define. I am thinking of accomplishing this like so:

In the model:

class User < ActiveRecord::Base
  @@invalid_usernames = %w()

  cattr_accessor :invalid_usernames

  validates :username, :exclusion { :in => @@invalid_usernames }
end

In some initializer:

User.invalid_usernames += Rails.application.routes.routes.map(&:path).join("\n").scan(/\s\/(\w+)/).flatten.compact.uniq

Is this "Rails way"? Is there a better way?

Here's my own answer, tested and working with Rails 3.1.3 and Ruby 1.9.3

app/models/user.rb

class User < ActiveRecord::Base
  class_attribute :invalid_usernames
  self.invalid_usernames = Set.new %w()

  validates :username, presence:   true,
                       uniqueness: { case_sensitive: false },
                       exclusion:  { in: lambda { self.invalid_usernames }}
end

config/application.rb

[:after_initialize, :to_prepare].each do |hook|
  config.send(hook) do
    User.invalid_usernames += Rails.application.routes.routes.map(&:path).join("\n").scan(/\s\/(\w+)/).flatten.compact.uniq
  end
end

Notes

I initially tried setting User.invalid_usernames during after_initialize but found it needs to be set during to_prepare (i.e. before each request in development mode, and before the first request in production mode) since the models are reloaded in development before each request and the original setting is lost.

I am however also setting User.invalid_usernames during after_initialize because the routes don't seem to be available during to_prepare when running in the test environment. Another workaround I tried for this, which does work, is to force load the routes during to_prepare:

config.to_prepare do
  Rails.application.reload_routes!
  User.invalid_usernames += Rails.application.routes.routes.map(&:path).join("\n").scan(/\s\/(\w+)/).flatten.compact.uniq
end

I like this because it's DRY and easy to read. But I'm wary of reloading the routes on every request, even if it is only in development mode. I'd rather use something a little harder to read if it means I fully understand the impact. Open to criticisms!

I also ditched cattr_accessor for class_attribute when I found out the former applies to the entire class hierarchy (i.e. changing its value on a subclass would affect the superclass)

I also chose to use a Set for User.invalid_usernames instead of an array as there's no need to store and compare against dupes and it was a transparent change.

I also changed to Ruby 1.9 hash syntax (:

needle/definition-context.rb:36: warning: undefining `initialize' may cause serious problems

5 votes

I installed a new gem needle in my rails 3.1.

It installed properly but when I start my rails using command rails server --debugger

I get the following warnings:

.rvm/gems/ruby-1.9.2-p290/gems/needle-1.3.0/lib/needle/definition-context.rb:36: warning: undefining `initialize' may cause serious problems
.rvm/gems/ruby-1.9.2-p290/gems/needle-1.3.0/lib/needle/definition-context.rb:36: warning: undefining 'object_id' may cause serious problems
.rvm/gems/ruby-1.9.2-p290/gems/needle-1.3.0/lib/needle/definition-context.rb:36: warning: undefining '__send__' may cause serious problems

How can I get rid of it?

The problem is within the needle gem itself. It does this:

public_instance_methods -
[ "instance_eval", "object_id", "__id__", "__send__", "initialize",
  "remove_const", "method_missing", "method", "class", "inspect", "to_s",
  "instance_variables", "block_given?" ]

But in Ruby 1.9, the public_instance_methods method returns objects of the Symbol variety, not String. So what happens is effectively this:

[:__send__, <and other methods>] - ["__send__", <and other methods>]
=> [:__send__, <and other methods>]

When it should be NOT removing those methods in the provided Array.

This indicates to me that the library hasn't been updated (or at least tested) for Ruby 1.9. I would recommend finding where the code is for this library, forking it and then applying a patch that converts the array to symbols using something like map(&:to_sym) to fix this problem.

But be aware: there may be other cases where these differences between 1.8 and 1.9 are present.