Tag: Ruby on Rails

Rails 4.0.1: Revert change on ActiveRecord::Relation#order method monkey patch to keep Rails 4.0.0 order behaviour

It is really good habit to review source code of each new Rails release (or at least a changelog file). Today while reviewing this release note, I've noticed, that the Rails team is going to revert the ActiveRecord#order functionality, so it will work like in the 3.2 version.

I must say, that I'm a bit disappointed. I really got used to this functionality and I used it really often. It was quite convenient to create scopes with default sorting, that could be easily "overwritten" by any other. Of course after that change I can still use the reorder method, to get exactly same effect, but it will require a lot of changes in the code. Also IMHO it seems kinda unfair - I put a lot of effort to migrate from Rails 3.2 to Rails 4.0.0 (stable) and it seems, that some of that work just got wasted, because Rails guys seem to be a bit unstable. I can understand behaviour changes between major releases, but this is just a fuck*ng small one!

105012-you-shall-deal-with-it

If you're not willing to spend a lot of time getting back to previous ordering mode (and dealing with it), you can use this monkey patch (put it into config/initializers) to keep the current (4.0.0) ordering behaviour:

module ActiveRecord
  class Relation

    def order!(*args)
      args.flatten!
      validate_order_args args

      references = args.reject { |arg| Arel::Node === arg }
      references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
      references!(references) if references.any?

      # if a symbol is given we prepend the quoted table name
      args = args.map { |arg|
        arg.is_a?(Symbol) ? "#{quoted_table_name}.#{arg} ASC" : arg
      }
      self.order_values = args + self.order_values
      self
    end

  end
end

Rafaels says that 4.0.1 ordering will stay as a default one, although I would not recommend doing any hasty moves now. I think it's worth waiting at least few months to find out, if they are not going to change it again soon.

Meanwhile you can review Github commit with this change. Also be prepared for the shitstorm that is coming on Thursday (4.0.1 release day)...

687474703a2f2f692e716b6d652e6d652f3335747837352e6a7067

Ransack – no translations for ActiveRecord nested models

Ransack is a great gem. You can consider it as a new better (with sparkles!) version of MetaSearch. Unfortunately, it seems that it is not translating ActiveRecord attributes, when they are nested.
For example:

module System
  class Setting < ActiveRecord::Base
    validates :name,
      presence: true
  end
end

We have a name attribute on a System::Setting model. According to Rails doc translation yaml for such a structure should be similar to this one:

pl:
  activerecord:
    attributes:
      "system/setting":
        name: Nazwa

Unluckily this won't work (it did with MetaSearch). When you look into Ransack /lib/ransack/translate.rb file, you will find such a piece of code (around line 25):

defaults = base_ancestors.map do |klass|
  :"ransack.attributes.#{klass.model_name.singular}.#{original_name}"
end

You can see, that Ransack is enclosing all attributes in it's own namespace. I can't do this, since I have gems that use "original" Rails convention (activerecord.attributes), so I had to replace those lines with:

defaults  = []
base_ancestors.map do |klass|
  defaults << :"activerecord.attributes.#{klass.model_name.i18n_key}.#{original_name}"
  defaults << :"ransack.attributes.#{klass.model_name.singular}.#{original_name}"
end

After that, Ransack is supporting both namespaces. The whole translate file that you should include in your project should look like this:

I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'locale', '*.yml')]

# This is a temporary workaround for this issue:
# https://github.com/ernie/ransack/issues/273
# It allows names to be translated in sort_link @search, :name based on their
# activerecord.attributes scope
module Ransack
  module Translate
    def self.attribute(key, options = {})
      unless context = options.delete(:context)
        raise ArgumentError, "A context is required to translate attributes"
      end

      original_name = key.to_s
      base_class = context.klass
      base_ancestors = base_class.ancestors.select { |x| x.respond_to?(:model_name) }
      predicate = Predicate.detect_from_string(original_name)
      attributes_str = original_name.sub(/_#{predicate}$/, '')
      attribute_names = attributes_str.split(/_and_|_or_/)
      combinator = attributes_str.match(/_and_/) ? :and : :or
      defaults  = []
      base_ancestors.map do |klass|
        defaults << :"activerecord.attributes.#{klass.model_name.i18n_key}.#{original_name}"
        defaults << :"ransack.attributes.#{klass.model_name.singular}.#{original_name}"
      end

      translated_names = attribute_names.map do |attr|
        attribute_name(context, attr, options[:include_associations])
      end

      interpolations = {}
      interpolations[:attributes] = translated_names.join(" #{Translate.word(combinator)} ")

      if predicate
        defaults << "%{attributes} %{predicate}"
        interpolations[:predicate] = Translate.predicate(predicate)
      else
        defaults << "%{attributes}"
      end

      defaults << options.delete(:default) if options[:default]
      options.reverse_merge! :count => 1, :default => defaults
      I18n.translate(defaults.shift, options.merge(interpolations))
    end

  end
end

Copyright © 2025 Closer to Code

Theme by Anders NorenUp ↑