Вопрос по activerecord, postgresql, ruby-on-rails, arel, sql – Как заставить установщик атрибута отправлять значение через функцию SQL

8

Я пытаюсь заставить установщик атрибута в модели ActiveRecord обернуть его значение в функции postgres text2ltree (), прежде чем rails сгенерирует свой sql-запрос.

Например,

<code>post.path = "1.2.3"
post.save
</code>

Должен генерировать что-то вроде

<code>UPDATE posts SET PATH=text2ltree('1.2.3') WHERE id = 123 # or whatever
</code>

Каков наилучший способ сделать это?

Вы можете написать функцию Postgres (хранимая процедура), а затем просто вызватьSELECT myfunc('1.2.3'), Я могу привести пример, если вы заинтересованы в этом маршруте. Erwin Brandstetter

Ваш Ответ

1   ответ
2

EDIT: Для достижения именно того, что вы ищете выше, вы можете использовать это, чтобы переопределить установщик по умолчанию в файле модели:

  self[:path] = connection.execute("SELECT text2ltree('#{value}');")[0][0]
end

Тогда код у вас выше работает.

Я заинтересован в том, чтобы узнать больше о внутренностях ActiveRecord и его непостижимых основах метапрограммирования, поэтому в качестве упражнения я попытался выполнить то, что вы описали в своих комментариях ниже. Вот пример, который работал для меня (это все в post.rb):

module DatabaseTransformation
  extend ActiveSupport::Concern

  module ClassMethods
    def transformed_by_database(transformed_attributes = {})

      transformed_attributes.each do |attr_name, transformation|

        define_method("#{attr_name}=") do |argument|
          transformed_value = connection.execute("SELECT #{transformation}('#{argument}');")[0][0]
          write_attribute(attr_name, transformed_value)
        end
      end
    end
  end
end

class Post < ActiveRecord::Base
  attr_accessible :name, :path, :version
  include DatabaseTransformation
  transformed_by_database :name => "length" 

end

Консольный вывод:

1.9.3p194 :001 > p = Post.new(:name => "foo")
   (0.3ms)  SELECT length('foo');
 => #<Post id: nil, name: 3, path: nil, version: nil, created_at: nil, updated_at: nil> 

В реальной жизни я полагаю, что вы хотитеinclude модуль в ActiveRecord :: Base, в файле где-то ранее в пути загрузки. Вы также должны правильно обработать тип аргумента, который вы передаете функции базы данных. Наконец я узнал, чтоconnection.execute реализуется каждым адаптером базы данных, поэтому способ доступа к результату может быть различным в Postgres (в данном примере это SQLite3, где набор результатов возвращается в виде массива хэшей, а ключ к первой записи данных равен 0).

Этот пост был невероятно полезным:

http://www.fakingfantastic.com/2010/09/20/concerning-yourself-with-active-support-concern/

как было руководство по Rails для создания плагинов:

http://guides.rubyonrails.org/plugins.html

Кроме того, я думаю, что в Postgres я все еще делаю это, используя миграцию для создания правила перезаписи запросов, но это принесло большой опыт обучения. Надеюсь, это сработает, и я могу перестать думать о том, как это сделать сейчас.

Да, я полагаю, я имел в виду больше второй. Этот вопрос больше связан с пониманием внутренних частей ActiveRecord / Arel, чем с чем-либо еще. Я одобрил ваш ответ, потому что вы предоставляете ценную информацию. Nick Colgan
Я чувствую, что здесь должен быть более эффективный способ сделать это, включающий в себя исправление обезьяны ActiveRecord / Arel Nick Colgan
Совершенно другой способ сделать это, о котором я только что подумал, - использовать механизм переписывания запросов Postgresql. Немного похоже на использование триггера. Вы просто настроили правило для перезаписи обновлений таким образом, чтобы значение передавалось через функцию. Плюс в том, что ваша модель чиста, и вы делаете только один вызов в базу данных. Недостатком является то, что это не зависит от базы данных, что, очевидно, не является проблемой в вашем случае. Я недостаточно знаком с синтаксисом, чтобы опубликовать пример, но здесь есть ссылка на документы:postgresql.org/docs/8.4/static/sql-createrule.html
Если "эффективным" Вы имеете в виду производительность, метод переписывания запросов превосходит; Я не вижу, как вы можете реализовать что-либо в Rails, чтобы избежать двух обращений к БД. Если "эффективным" Вы имеете в виду СУХОЙ / более выразительный (при условии, что это не единственное место, где вы когда-либо захотите это сделать), тогда я согласен, что это вариант.

Похожие вопросы