2009-03-10 2 views
117

У меня есть некоторые модели, которые имеют after_save обратные вызовы. Обычно это нормально, но в некоторых ситуациях, например при создании данных разработки, я хочу сохранить модели без выполнения обратных вызовов. Есть ли простой способ сделать это? Что-то похожее на ...Как избежать обратных вызовов ActiveRecord?

Person#save(:run_callbacks => false) 

или

Person#save_without_callbacks 

Я посмотрел в документации Rails и ничего не нашли. Однако, по моему опыту, Rails-документы не всегда рассказывают всю историю.

UPDATE

Я нашел a blog post, что объясняет, как можно удалить обратные вызовы из модели, как это:

Foo.after_save.clear 

Я не мог найти, где документально подтверждено, что метод, но это, кажется, работает.

+8

Если вы делаете что-то деструктивное или дорогие (например, отправка сообщений электронной почты) в функции обратного вызова я рекомендую движущемся это выход и запуск его отдельно от контроллера или в другом месте. Таким образом, вы не будете «случайно» запускать его в процессе разработки и т. Д. – ryanb

+1

Решение, которое вы приняли, не работает для меня. Я использую rails 3. Я получаю ошибку, подобную этой: - undefined method 'update_without_callbacks 'для # <Пользователь: 0x10ae9b848> –

+0

yaa, что сообщение в блоге работало .... –

ответ

69

Это решение Rails 2 только.

Я только что исследовал это, и я думаю, что у меня есть решение. Есть два частных методов ActiveRecord, которые вы можете использовать:

update_without_callbacks 
create_without_callbacks 

Вы собираетесь должны использовать отправить вызывать эти методы. примеры:

p = Person.new(:name => 'foo') 
p.send(:create_without_callbacks) 

p = Person.find(1) 
p.send(:update_without_callbacks) 

Это определенно то, что вы действительно хотите использовать только в консоли или при выполнении некоторых случайных тестов. Надеюсь это поможет!

+7

его не работает для меня. Я использую rails 3. Я получаю ошибку, например: undefined method 'update_without_callbacks 'для # <Пользователь: 0x10ae9b848> –

+0

Ваше предложение не работает, но сообщение в блоге, упомянутое в части обновления, работает. –

+0

Это будет пропустить проверки. –

-2

Не самый чистый способ, но вы можете обернуть код обратного вызова в состояние, проверяющее среду Rails.

if Rails.env == 'production' 
    ... 
15

Вы могли бы попробовать что-то подобное в вашей модели Person:

after_save :something_cool, :unless => :skip_callbacks 

def skip_callbacks 
    ENV[RAILS_ENV] == 'development' # or something more complicated 
end 

EDIT: after_save это не символ, но это по крайней мере, время однотысячным я пытался сделать это один.

+0

Я действительно думаю, что это лучший ответ здесь. Таким образом, в модели доступна логика, которая определяет, когда обратный вызов пропускается, и у вас нет сумасшедших фрагментов кода во всем мире, отбрасывая бизнес-логику или обходя инкапсуляцию с помощью 'send'. KOODOS – Ziggy

5

Единственный способ предотвратить все последующие обратные вызовы after_save - вернуть первое значение false.

Может быть, вы могли бы попробовать что-то вроде (непроверенные):

class MyModel < ActiveRecord::Base 
    attr_accessor :skip_after_save 

    def after_save 
    return false if @skip_after_save 
    ... blah blah ... 
    end 
end 

... 

m = MyModel.new # ... etc etc 
m.skip_after_save = true 
m.save 
+0

Мне нравится пробовать (непроверено). Прогулка по треку. – Adamantish

0

Почему вы хотите, чтобы иметь возможность сделать это в процессе разработки? Конечно, это будет означать, что вы создаете свое приложение с недопустимыми данными и, как таковое, оно будет вести себя странно, а не так, как вы ожидаете в процессе производства.

Если вы хотите заполнить свои данные db данными, лучшим подходом было бы создание задачи рейка, которая использовала бы камень faker для создания достоверных данных и импорта в db, создавая столько или несколько записей, сколько вы хотите, но если вы на него наклонены, и у меня есть веская причина, я полагаю, что update_without_callbacks и create_without_callbacks будут работать нормально, но когда вы пытаетесь согнуть рельсы по своему усмотрению, спросите себя, что у вас есть веская причина, и если то, что вы делаете, действительно хорошая идея.

+0

Я не пытаюсь сэкономить без проверок, без обратных вызовов. Мое приложение использует обратные вызовы для записи некоторого статического HTML в файловую систему (вроде CMS). Я не хочу этого делать при загрузке данных dev. – Ethan

+0

Была просто мыслью, я думаю, всякий раз, когда в прошлом я видел такой вопрос, она пытается обойти вещи по плохим причинам. – nitecoder

0

Другим способом было бы использовать контрольные крючки вместо обратных вызовов. Например:

class Person < ActiveRecord::Base 
    validate_on_create :do_something 
    def do_something 
    "something clever goes here" 
    end 
end 

Таким образом, вы можете получить do_something по умолчанию, но вы можете легко изменить его с помощью:

@person = Person.new 
@person.save(false) 
+2

Это кажется плохой идеей - вы должны использовать вещи по назначению. Последнее, что вы хотите, это ваши подтверждения наличия побочных эффектов. – chug2k

5

Похоже, один из способов справиться с этим в Rails 2.3 (с update_without_callbacks больше нет и т. д.), будет использовать update_all, который является одним из методов, который пропускает обратные вызовы в соответствии с section 12 of the Rails Guide to validations and callbacks.

Также обратите внимание, что если вы делаете что-то в своем обратном вызове after_, который выполняет вычисления на основе многих ассоциаций (например, has_many, где вы также принимаете accept_nested_attributes_for), вам необходимо перезагрузить ассоциацию в случае, если часть сохранения, один из ее членов был удален. не

1

Ни одна из этих точек в without_callbacks плагин, который просто делает то, что вам нужно ...

class MyModel < ActiveRecord::Base 
    before_save :do_something_before_save 

    def after_save 
    raise RuntimeError, "after_save called" 
    end 

    def do_something_before_save 
    raise RuntimeError, "do_something_before_save called" 
    end 
end 

o = MyModel.new 
MyModel.without_callbacks(:before_save, :after_save) do 
    o.save # no exceptions raised 
end 

http://github.com/cjbottaro/without_callbacks работы с Rails 2.x

19

рельсы 3:

MyModel.send("_#{symbol}_callbacks") # list 
MyModel.reset_callbacks symbol # reset 
+11

Ницца. Также MyModel.skip_callback (: create,: after,: my_callback) для точного контроля .. см. ActiveSupport :: Callbacks :: Документы ClassMethods для всех lobang – tardate

+4

Полезная информация: «символ» в 'reset_callbacks' не': after_save' , а скорее ': save'. http://apidock.com/rails/v3.0.9/ActiveSupport/Callbacks/ClassMethods/reset_callbacks – nessur

1
# for rails 3 
    if !ActiveRecord::Base.private_method_defined? :update_without_callbacks 
    def update_without_callbacks 
     attributes_with_values = arel_attributes_values(false, false, attribute_names) 
     return false if attributes_with_values.empty? 
     self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values) 
    end 
    end 
0

Один из вариантов - иметь отдельную модель для таких манипуляций, используя ту же таблицу:

class NoCallbacksModel < ActiveRecord::Base 
    set_table_name 'table_name_of_model_that_has_callbacks' 

    include CommonModelMethods # if there are 
    : 
    : 

end 

(Тот же подход может сделать вещи проще для обхода валидаций)

Стефан

204

Использование update_column (Rails> = v3.1) или update_columns (Rails> = 4,0), чтобы пропустить обратные вызовы и проверки , Также с этими методами, updated_at is не обновлен.

#Rails >= v3.1 only 
@person.update_column(:some_attribute, 'value') 
#Rails >= v4.0 only 
@person.update_columns(attributes) 

http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update_column

# 2: Пропуск обратных вызовов, которые также работает во время создания объекта

class Person < ActiveRecord::Base 
    attr_accessor :skip_some_callbacks 

    before_validation :do_something 
    after_validation :do_something_else 

    skip_callback :validation, :before, :do_something, if: :skip_some_callbacks 
    skip_callback :validation, :after, :do_something_else, if: :skip_some_callbacks 
end 

person = Person.new(person_params) 
person.skip_some_callbacks = true 
person.save 
+0

Чистый и элегантный. Благодаря! –

+2

похоже, что он работает с 2.x, а также существует множество других методов, которые работают аналогично: http://guides.rubyonrails.org/active_record_validations_callbacks.html#skipping-callbacks – rogerdpack

+14

Это не относится к:: create_without_callbacks ':(Как я могу запустить что-то похожее на это? (Работал в Rails2, удален в Rails3). – nzifnab

1

Вы можете использовать подлый сохранение перл: https://rubygems.org/gems/sneaky-save.

Примечание. Это не может помочь в сохранении ассоциаций без проверки. Он выдает ошибку «created_at не может быть null», поскольку он непосредственно вставляет sql-запрос в отличие от модели. Чтобы реализовать это, нам нужно обновить все автоматически сгенерированные столбцы db.

+1

Этот жемчуг больше не работает – Altonymous

23

Обновлено:

решение @Vikrant Чаудхари кажется лучше:

#Rails >= v3.1 only 
@person.update_column(:some_attribute, 'value') 
#Rails >= v4.0 only 
@person.update_columns(attributes) 

Мой первоначальный ответ:

смотрите по этой ссылке: How to skip ActiveRecord callbacks?

в R ails3,

предположим, что мы имеем определение класса:

class User < ActiveRecord::Base 
    after_save :generate_nick_name 
end 

Approach1:

User.send(:create_without_callbacks) 
User.send(:update_without_callbacks) 

Approach2: Если вы хотите, чтобы пропустить их в RSpec файлов или любой другой, попробуйте следующее:

User.skip_callback(:save, :after, :generate_nick_name) 
User.create!() 

ПРИМЕЧАНИЕ: как только это будет сделано, если вы не находитесь в среде rspec, вы hould сброса обратных вызовов:

User.set_callback(:save, :after, :generate_nick_name) 

отлично работает для меня на рельсах 3.0.5

4

https://gist.github.com/576546

просто свалка эта обезьяна-патч в конфиге/Инициализаторы/skip_callbacks.rb

затем

Project.skip_callbacks { @project.save }

или тому подобное.

все заслуга автора

1

Я написал плагин, который реализует update_without_callbacks в Rails 3:

http://github.com/dball/skip_activerecord_callbacks

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

1

Если вы используете Rails 2. Вы можете использовать SQL-запрос для обновления столбца без выполнения обратных вызовов и проверок.

YourModel.connection.execute("UPDATE your_models SET your_models.column_name=#{value} WHERE your_models.id=#{ym.id}") 

Я думаю, что он должен работать в любых версиях рельсов.

2

Решение, которое должно работать во всех версиях Rails без использования самоцвета или плагина, - это просто выпустить заявления об обновлении напрямую. например

ActiveRecord::Base.connection.execute "update table set foo = bar where id = #{self.id}" 

Это может быть (или не обязательно) вариант в зависимости от сложности вашего обновления.Это хорошо работает, например, для обновления флагов в записи от в пределах обратный вызов after_save (без перезапуска обратного вызова).

+0

Не знаете, почему нисходящий сигнал, но я все же считаю, что приведенный выше ответ является законным. Иногда лучший способ избежать проблем с поведением ActiveRecord - избегать использования ActiveRecord. –

+0

Направлено на принцип, чтобы противостоять -1. У нас просто была проблема с производством (с длинной историей), которая требовала от нас создания новой записи (а не обновления), и обратные вызовы обстрелов были бы катастрофическими. Все приведенные выше ответы - это хаки, признают они это или нет, и переход к БД был лучшим решением. Для этого существуют законные условия. Хотя следует остерегаться SQL-инъекции с помощью '# {...}'. – sinisterchipmunk

8

Вы можете использовать update_columns:

User.first.update_columns({:name => "sebastian", :age => 25}) 

обновляет данные атрибуты объекта, не вызывая сохранения, поэтому пропуск валидаций и обратных вызовов.

1

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

Модель:

class MyModel < ActiveRecord::Base 
    before_save :do_stuff, unless: :skip_do_stuff_callback 
    attr_accessor :skip_do_stuff_callback 

    def do_stuff 
    puts 'do stuff callback' 
    end 
end 

Тест:

m = MyModel.new() 

# Fire callbacks 
m.save 

# Without firing callbacks 
m.skip_do_stuff_callback = true 
m.save 

# Fire callbacks again 
m.skip_do_stuff_callback = false 
m.save 
0

Для создания тестовых данных в Rails использовать этот хак:

record = Something.new(attrs) 
ActiveRecord::Persistence.instance_method(:create_record).bind(record).call 

https://coderwall.com/p/y3yp2q/edit

9

Если целью является просто вставить запись без обратных вызовов или проверок, и вы хотели бы сделать это, не прибегая к дополнительным драгоценным камням, добавляя условные проверки, используя RAW SQL, или futzing с вашим выходным кодом каким-либо образом, подумайте об использовании «тени» объект ", указывающий на вашу существующую таблицу db. Как так:

class ImportedPerson < ActiveRecord::Base 
    self.table_name = 'people' 
end 

Это работает с любой версией Rails, является поточно, и полностью устраняет все валидации и обратные вызовы без каких-либо изменений в существующем код. Вы можете просто подбросить это объявление класса прямо перед вашим фактическим импортом, и вам должно быть хорошо идти. Только не забудьте использовать новый класс, чтобы вставить объект, например:

ImportedPerson.new(person_attributes) 
+1

Лучшее решение КОГДА-ЛИБО. Элегантный и простой! –

0

Что-то, что должно работать со всеми версиями ActiveRecord без зависимости от параметров или методов ActiveRecord, которые могут или не могут существовать.

module PlainModel 
    def self.included(base) 
    plainclass = Class.new(ActiveRecord::Base) do 
     self.table_name = base.table_name 
    end 
    base.const_set(:Plain, plainclass) 
    end 
end 


# usage 
class User < ActiveRecord::Base 
    include PlainModel 

    validates_presence_of :email 
end 

User.create(email: "")  # fail due to validation 
User::Plain.create(email: "") # success. no validation, no callbacks 

user = User::Plain.find(1) 
user.email = "" 
user.save 

TLDR: использовать "другую модель ActiveRecord" за тот же стол

1

мне нужно решение для Rails 4, так что я пришел с этим:

приложение/модели/касается/save_without_callbacks .RB

module SaveWithoutCallbacks 

    def self.included(base) 
    base.const_set(:WithoutCallbacks, 
     Class.new(ActiveRecord::Base) do 
     self.table_name = base.table_name 
     end 
    ) 
    end 

    def save_without_callbacks 
    new_record? ? create_without_callbacks : update_without_callbacks 
    end 

    def create_without_callbacks 
    plain_model = self.class.const_get(:WithoutCallbacks) 
    plain_record = plain_model.create(self.attributes) 
    self.id = plain_record.id 
    self.created_at = Time.zone.now 
    self.updated_at = Time.zone.now 
    @new_record = false 
    true 
    end 

    def update_without_callbacks 
    update_attributes = attributes.except(self.class.primary_key) 
    update_attributes['created_at'] = Time.zone.now 
    update_attributes['updated_at'] = Time.zone.now 
    update_columns update_attributes 
    end 

end 

в любой модели:

include SaveWithoutCallbacks 

, то вы можете:

record.save_without_callbacks 

или

Model::WithoutCallbacks.create(attributes) 
3

Наиболее up-voted ответ может показаться запутанным в некоторых случаях.

Вы можете использовать только простой if чек, если вы хотите, чтобы пропустить обратный вызов, как это:

after_save :set_title, if: -> { !new_record? && self.name_changed? } 
Смежные вопросы