tng-rails-code-snippets-4#

SCOPE#

1#

class Person
  scope :grouped_counts, group(:name).count
end

It does however work as a class method#

def self.grouped_counts
  group(:name).count
end

In Rails 3, everything returns an ActiveRecord::Relation until you need the actual results, so scopes can be chained against class methods and vice versa (as long as the class methods return ActiveRecord::Relation objects, not some other object type (like a count)).

As Dylan alluded to in his answer, one difference between scope and class method is that scopes are evaluated when the class is loaded. This may lead to unexpected result.#

For example,

class Post < ActiveRecord::Base
    scope :published_earlier, where('published_at < ?', Date.today)
end

is prone to error. The correct way is to use a lambda

class Post < ActiveRecord::Base
    scope :published_earlier, -> { where('published_at < ?', Date.today) }
end

Lambda block is lazily evaluated. So Date.today is run when you call the scope, not when the class is evaluated.

If you use a class method, then you don't need to use lambda.

class Post < ActiveRecord::Base
    def self.published_earlier
        where('published_at < ?', Date.today)
    end
end

Because with class method, the code is run at the time of method call.

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

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.

How to json a computed field#

You can overwrite the to_json method in your model by passing :methods options and call super. This will call as_json version of the super class with :methods options so that it will serialize your model with full_name attributes.

class Person < ActiveRecord::Base
  def as_json(options={})
    options[:methods] = [:full_name]
    super
  end

  def full_name
    "#{first_name} #{last_name}"
  end
end

Check this document out if you want to know more options that can pass to as_json method.

Inside your controller, you can simply

render :json => @person

Specifically the :methods hash:

respond_with({
  :cars => @cars.as_json(:only => [:make, :model], :methods => [:full_name]),
  :vans => @vans
})

Alternatively#

What you can do is have a setter method in your User class like so

def full_name=(string)
  names = string.split # the default delimiter is a space
  self.update_attributes(:first_name => names[0], :last_name => names[1])
end

The form should then refer to :full_name as a field, and even though it's not an actual column, Rails will automatically access this method above and execute it, thereby updating the columns you actually want.