Here at Plataformatec we use Github Pull Requests a lot for code review and this usually yields tons of constructive comments and excellent discussions from time to time. One of the recent topics was about whether we should use scopes or class methods throughout the project to be consistent. It’s also not hard to find discussions about it all over the internet. The classic comment usually boils down to “there is no difference between them” or “it is a matter of taste”. I tend to agree with both sentences, but I’d like to show some slight differences that exist between both.

Defining a scope

First of all, lets get a better understanding about how scopes are used. In Rails 3 you can define a scope in two ways:

class Post < ActiveRecord::Base
  scope :published, where(status: 'published')
  scope :draft, -> { where(status: 'draft') } 
end

The main difference between both usages is that the :published condition is evaluated when the class is first loaded, whereas the :draft one is lazy evaluated when it is called. Because of that, in Rails 4 the first way is going to be deprecated which means you will always need to declare scopes with a callable object as argument. This is to avoid issues when trying to declare a scope with some sort of Time argument:

class Post < ActiveRecord::Base
  scope :published_last_week, where('published_at >= ?', 1.week.ago)
end

Because this won’t work as expected: 1.week.ago will be evaluated when the class is loaded, not every time the scope is called.

Scopes are just class methods

Internally Active Record converts a scope into a class method. Conceptually, its simplified implementation in Rails master looks something like this:

def self.scope(name, body)
  singleton_class.send(:define_method, name, &body)
end

Which ends up as a class method with the given name and body, like this:

def self.published
  where(status: 'published')
end

And I think that’s why most people think: “Why should I use a scope if it is just syntax sugar for a class method?”. So here are some interesting examples for you to think about.

Scopes are always chainable

Lets use the following scenario: users will be able to filter posts by statuses, ordering by most recent updated ones. Simple enough, lets write scopes for that:

class Post < ActiveRecord::Base
  scope :by_status, -> status { where(status: status) }
  scope :recent, -> { order("posts.updated_at DESC") }
end

And we can call them freely like this:

Post.by_status('published').recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' 
#   ORDER BY posts.updated_at DESC

Or with a user provided param:

Post.by_status(params[:status]).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' 
#   ORDER BY posts.updated_at DESC

So far, so good. Now lets move them to class methods, just for the sake of comparing:

class Post < ActiveRecord::Base
  def self.by_status(status)
    where(status: status)
  end
 
  def self.recent
    order("posts.updated_at DESC")
  end
end

Besides using a few extra lines, no big improvements. But now what happens if the :status parameter is nil or blank?

Post.by_status(nil).recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" IS NULL 
#   ORDER BY posts.updated_at DESC
 
Post.by_status('').recent
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = '' 
#   ORDER BY posts.updated_at DESC

Oooops, I don’t think we wanted to allow these queries, did we? With scopes, we can easily fix that by adding a presence condition to our scope:

scope :by_status, -> status { where(status: status) if status.present? }

There we go:

Post.by_status(nil).recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC
 
Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC

Awesome. Now lets try to do the same with our beloved class method:

class Post < ActiveRecord::Base
  def self.by_status(status)
    where(status: status) if status.present?
  end
end

Running this:

Post.by_status('').recent
NoMethodError: undefined method `recent' for nil:NilClass

And :bomb:. The difference is that a scope will always return a relation, whereas our simple class method implementation will not. The class method should look like this instead:

def self.by_status(status)
  if status.present?
    where(status: status)
  else
    all
  end
end

Notice that I’m returning all for the nil/blank case, which in Rails 4 returns a relation (it previously returned the Array of items from the database). In Rails 3.2.x, you should use scoped there instead. And there we go:

Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC

So the advice here is: never return nil from a class method that should work like a scope, otherwise you’re breaking the chainability condition implied by scopes, that always return a relation.

Scopes are extensible

Lets get pagination as our next example and I’m going to use the kaminari gem as basis. The most important thing you need to do when paginating a collection is to tell which page you want to fetch:

Post.page(2)

After doing that you might want to say how many records per page you want:

Post.page(2).per(15)

And you may to know the total number of pages, or whether you are in the first or last page:

posts = Post.page(2)
posts.total_pages # => 2
posts.first_page? # => false
posts.last_page?  # => true

This all makes sense when we call things in this order, but it doesn’t make any sense to call these methods in a collection that is not paginated, does it? When you write scopes, you can add specific extensions that will only be available in your object if that scope is called. In case of kaminari, it only adds the page scope to your Active Record models, and relies on the scope extensions feature to add all other functionality when page is called. Conceptually, the code would look like this:

scope :page, -> num { # some limit + offset logic here for pagination } do
  def per(num)
    # more logic here
  end
 
  def total_pages
    # some more here
  end
 
  def first_page?
    # and a bit more
  end
 
  def last_page?
    # and so on
  end
end

Scope extensions is a powerful and flexible technique to have in our toolchain. But of course, we can always go wild and get all that with class methods too:

def self.page(num)
  scope = # some limit + offset logic here for pagination
  scope.extend PaginationExtensions
  scope
end
 
module PaginationExtensions
  def per(num)
    # more logic here
  end
 
  def total_pages
    # some more here
  end
 
  def first_page?
    # and a bit more
  end
 
  def last_page?
    # and so on
  end
end

It is a bit more verbose than using a scope, but it yields the same results. And the advice here is: pick what works better for you but make sure you know what the framework provides before reinventing the wheel.

Wrapping up

I personally tend to use scopes when the logic is very small, for simple where/order clauses, and class methods when it involves a bit more complexity, but whether it receives an argument or not doesn’t really matter much to me. I also tend to rely more on scopes when doing extensions like showed here, since it’s a feature that Active Record already gives us for free.

I think it’s important to clarify the main differences between scopes and class methods, so that you can pick the right tool for the job™, or the tool that makes you more comfortable. Whether you use one or another, I don’t think it really matters, as long as you write them clear and consistently throughout your application.

Do you have any thought about using scopes vs class methods? Make sure to leave a comment below telling us what you think, we’d love to hear.

Hi everybody.

I’d like to announce that Devise v2.2.3, v2.1.3, v2.0.5 and v1.5.4 have been released with a security patch. Upgrade immediately unless you are using PostgreSQL or SQLite3. Users of all other databases (including NoSQL ones) require immediate upgrade.

Using a specially crafted request, an attacker could trick the database type conversion code to return incorrect records. For some token values this could allow an attacker to bypass the proper checks and gain control of other accounts.

In case you are using a Devise series older than the ones listed above, recommendations are provided below back to v1.2 series. Regardless, an upgrade to more recent versions is advised.

Versions affected

We checked all Devise versions released in the previous two years and recommendations follows as below.

v1.5, v2.0, v2.1 and v2.2 series

You can upgrade to any of v2.2.3, v2.1.3, v2.0.5 and v1.5.4. In case an upgrade is not feasible, please add the following patch to config/initializers/devise_patch.rb inside your Rails application:

Devise::ParamFilter.class_eval do
  def param_requires_string_conversion?(_value); true; end
end

v1.4 series

Please add the following patch to config/initializers/devise_patch.rb inside your Rails application:

Devise::Models::Authenticatable::ClassMethods.class_eval do
  def auth_param_requires_string_conversion?(value); true; end
end

Please upgrade to more recent versions.

v1.2 and v1.3 series

Not affected by this vulnerability. Please upgrade to more recent versions.

Upgrade notice

When upgrading to any of v2.2.3, v2.1.3, v2.0.5 and v1.5.4, some people may be relying on some wrong behaviour to filter data retrieved on authentication. For example, one may have writen in his model:

def find_for_authentication(conditions)
  conditions[:active] = true
  super
end

The code above may no longer work and needs to be rewriten as:

def find_for_authentication(conditions)
  find_first_by_auth_conditions(conditions, active: true)
end

Thank you notes

We would like to thank joernchen of Phenoelit for disclosing this vulnerability and working with us on a patch.

Em 2009, quando fundamos a Plataformatec, havia muita incerteza e desafios pela frente. Por outro lado havia pontos claros para nós fundadores, principalmente quando o assunto eram as contratações do nosso time. Era unânime que não contrataríamos “recursos” e sim pessoas com as quais nós teríamos prazer e confiança em trabalhar juntos. Afinal de contas, o nosso modelo de negócios sempre foi lastreado na construção da nossa marca através da qualidade dos serviços que prestamos. Sendo assim, faz total sentido investir nossos esforços na retenção e desenvolvimento do nosso time.

Quatro anos depois aqui estamos, com um considerável histórico de projetos bem sucedidos e algo que consideramos muito valioso: o nosso Time. Quem conhece um pouco sobre empresas de “Outsourcing de TI” sabe que o turnover é altíssimo, mais que o triplo da média de outros setores. Honestamente, é uma lógica que nunca entendemos. Aqui na Plataformatec, não tivemos nenhum desligamento em 2012 e em nossos quatro anos de existência, somente um dos nossos engenheiros optou em sair da Plataformatec (aliás, ele é um grande amigo nosso e sempre que está no Brasil aparece para comer uma pizza conosco). Já houve pessoas que nos perguntaram qual é a mágica. A resposta é óbvia: não tem mágica. Nós somente convidamos pessoas a juntarem-se ao nosso time quando sentimos que há um grande fit cultural.

Plataformatec_team

Portanto se você acredita em colaboração, tem alta capacidade de aprendizado e tem vontade de trabalhar com um time com histórico de sucesso comprovado, continue lendo. Você pode ser o próximo integrante do nosso time. :)

Como participar do processo seletivo
Para participar do processo seletivo visite http://plataformatec.com.br/careers ou também veja http://www.facebook.com/plataformatec.com.br para conhecer mais sobre o nosso time.

Vagas abertas
Estamos à procura de:

  • Desenvolvedores de Software (Ruby e Rails)
  • Front-end Developers
  • Scrum Masters / Gerente de Projetos
  • Business Analysts (extração de requisitos)

Requisitos para candidatar-se (todas as vagas)

Para qualquer uma das vagas:
- Inglês Intermediário
- Local de trabalho: São Paulo, bairro Vila Madalena
- Pelo menos um ano de experiência na sua área

Requisitos complementares para candidatos a Desenvolvedores de Software
- Background em programação orientada a objetos
- Pelo menos um ano de experiência com desenvolvimento de aplicativos web em qualquer linguagem
- Pelo menos seis meses de experiência com Ruby e Rails
- OS Linux ou Mac
Mesmo que você não tenha todos os requisitos acima, mas realmente quer trabalhar conosco e ama programação, você está convidado a participar do nosso processo seletivo.

Estamos esperando por você!

Processo seletivo - http://plataformatec.com.br/careers
Facebook page - http://www.facebook.com/plataformatec.com.br.

[curiosidade] No blog post de dois anos atrás ainda dava para enxergar o sofá nas fotos. :)

We cannot express how excited we are with such great news. In our last blog post we were celebrating Rafael’s achievement and just a few months later we are celebrating again. Years ago, having three Plataformatec teammates as Rails Core members would be something we’d only dream of, but in 2012 it became reality. I must say that it didn’t happen by chance, not at all.

Carlos Antonio da Silva Rails Core team member

@cantoniodasilva

Carlos has always been an outstanding software developer and a great teammate. He is someone we all deeply trust to solve hard problems and deliver mission critical projects. We’ve always watched Carlos working really hard and we must say that he deserved every bit of such achievement. You can check out his contributions into many other open source projects at http://github.com/carlosantoniodasilva

Kudos to our friend @cantoniodasilva!!! :D

 

Last May we happily announced that Rafael França and Carlos Antonio earned commit access to the Ruby on Rails repository – it was a great accomplishment that deserved its own blog post. Today, we have some great news and we want to share with our readers. Just a few days ago, our team mate Rafael França (@rafaelfranca) received an invitation to join the Rails Core Team. And, of course, he accepted!

Rafael contributed to different features coming up in the next Rails 4 release and worked extensively with other Rails contributors to smash bugs and provide many improvements to the framework. I am personally happy to have a friend joining me in the Rails Core Team.

Furthermore, Rafael França’s contributions go beyond Rails. Lately he has also contributed to many other open source projects, like Mongoid, Janky and Dalli besides Plataformatec’s own projects like Devise, Simple Form and Elixir.

Rafael, congratulations! :D

From all your friends at Plataformatec.

It is common for web applications to interface with external services. When testing, since depending on an external service is very fragile, we end up mocking the interaction with such services. However, once in a while, it is still a good idea to check if the contract between your application and the service is still valid.

For example, this week we had to interact with a SOAP service, let’s call it KittenInfo (why would someone provide kitten information via a SOAP service is beyond the scope of this blog post). We only need to contact one end-point of the KittenInfo and it is called get_details, which receives a kitten identifier and returns kitten information:

KittenInfo::Client.new.get_details("gorbypuff")

Since this API is simple, it is very easy to mock the client whenever it is required by our application. On the other hand, we still need to verify that the integration between KittenInfo SOAP service and our application works correctly, so we write some tests for it:

describe KittenInfo::Client do
  it "retrieves kitten details" do
    client  = KittenInfo::Client.new
    details = client.get_details("gorbypuff")
    details[:owner].should == "tenderlove"
  end
end

However, since this is actually contacting the SOAP Service, it may make your test suite more fragile and slower, even more in this case, in which the SOAP Service responses take as long as kitten’s staring contests.

One possible solution to this problem is to make use of filter tags to exclude the SOAP integration tests from running, except when explicitly desired. We could do this by simply setting:

describe KittenInfo::Client, external: true do
  # ...
end

Then, in your spec_helper.rb, just set:

RSpec.configure do |config|
  config.filter_run_excluding external: true
end

Now, running your specs will by default skip all groups that have :external set to true. Whenever you tweak the client, or in your builds, you can run those specific tests with:

$ rspec --tag external

Notice that filter mechanism is similar to how we enable JavaScript tests when using Capybara. This means that, when using Capybara, you could also run all JavaScript tests in your app via $ rspec --tag js or all non-JavaScript tests with $ rspec --tag ~js.

What about you? What is your favorite RSpec trick?