Làm cách nào để tránh chạy lại cuộc gọi lại ActiveRecord?


140

Tôi có một số mô hình có các cuộc gọi lại after_save. Thông thường thì không sao, nhưng trong một số trường hợp, như khi tạo dữ liệu phát triển, tôi muốn lưu các mô hình mà không cần phải gọi lại. Có một cách đơn giản để làm điều đó? Một cái gì đó giống với ...

Person#save( :run_callbacks => false )

hoặc là

Person#save_without_callbacks

Tôi đã xem tài liệu Rails và không tìm thấy gì. Tuy nhiên, theo kinh nghiệm của tôi, các tài liệu Rails không phải lúc nào cũng kể toàn bộ câu chuyện.

CẬP NHẬT

Tôi đã tìm thấy một bài đăng trên blog giải thích cách bạn có thể xóa các cuộc gọi lại từ một mô hình như thế này:

Foo.after_save.clear

Tôi không thể tìm thấy nơi mà phương pháp đó được ghi lại nhưng nó dường như hoạt động.


8
Nếu bạn đang làm một cái gì đó phá hoại hoặc tốn kém (như gửi email) trong một cuộc gọi lại, tôi khuyên bạn nên chuyển cái này ra và kích hoạt nó một cách riêng biệt từ bộ điều khiển hoặc ở nơi khác. Bằng cách này, bạn sẽ không "vô tình" kích hoạt nó trong quá trình phát triển, v.v.
ryanb

2
giải pháp bạn chấp nhận là không làm việc cho tôi. Tôi đang sử dụng rails 3. Tôi đang gặp một lỗi như thế này: - phương thức không xác định `update_without_callbacks 'cho # <Người dùng: 0x10ae9b848>
Mohit Jain

yaa bài viết trên blog đã hoạt động ....
Mohit Jain


Sẽ không Foo.after_save.clearloại bỏ các cuộc gọi lại cho toàn bộ mô hình? Và sau đó làm thế nào để bạn đề xuất để khôi phục chúng?
Joshua Pinter

Câu trả lời:


72

Giải pháp này chỉ là Rails 2.

Tôi chỉ điều tra điều này và tôi nghĩ rằng tôi có một giải pháp. Có hai phương thức riêng tư ActiveRecord mà bạn có thể sử dụng:

update_without_callbacks
create_without_callbacks

Bạn sẽ phải sử dụng gửi để gọi các phương thức này. ví dụ:

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

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

Đây chắc chắn là thứ mà bạn chỉ thực sự muốn sử dụng trong bảng điều khiển hoặc trong khi thực hiện một số thử nghiệm ngẫu nhiên. Hi vọng điêu nay co ich!


7
nó không làm việc cho tôi Tôi đang sử dụng rails 3. Tôi đang gặp một lỗi như thế này: - phương thức không xác định `update_without_callbacks 'cho # <Người dùng: 0x10ae9b848>
Mohit Jain

Đề xuất của bạn không hoạt động nhưng bài đăng trên blog được đề cập trong phần cập nhật đang hoạt động ..
Mohit Jain

Điều này sẽ bỏ qua xác nhận, quá.
Daniel Pietzsch

Tôi có một giải pháp khác cho bất kỳ phiên bản nào của Rails. Nó hoạt động tốt cho chúng tôi. Kiểm tra nó trong bài viết trên blog của tôi: railsguides.net/2014/03/25/skip-callbacks-in-tests
ka8725

224

Sử dụng update_column (Rails> = v3.1) hoặc update_columns(Rails> = 4.0) để bỏ qua các cuộc gọi lại và xác nhận. Ngoài ra với phương pháp này, updated_atkhông được cập nhật.

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

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

# 2: Bỏ qua các cuộc gọi lại cũng hoạt động trong khi tạo một đối tượng

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

2
có vẻ như nó cũng hoạt động với 2.x và có một loạt các phương thức khác hoạt động tương tự: guide.rubyonrails.org/iêu
rogerdpack

15
Đây không phải là địa chỉ :create_without_callbacks:( Làm cách nào tôi có thể chạy một cái gì đó tương tự? (Làm việc trong Rails2, đã xóa trong Rails3).
nzifnab

Giả sử @personlà một biến trong bộ điều khiển ở đâu đó, giải pháp này có nghĩa là những người đọc lớp mô hình của bạn sẽ không thể hiểu được các cuộc gọi lại. Họ sẽ thấy after_create :something_coolvà sẽ nghĩ "tuyệt vời, một cái gì đó tuyệt vời sẽ xảy ra sau khi tạo ra!". Để thực sự hiểu lớp mô hình của bạn, họ sẽ phải grep qua tất cả các bộ điều khiển của bạn, tìm kiếm tất cả những nơi nhỏ mà bạn đã quyết định tiêm logic. Tôi không thích nó> o <;;
Ziggy

1
thay thế skip_callback ..., if: :skip_some_callbacksbằng after_create ..., unless: :skip_some_callbacksđể chạy đúng với after_create.
sakurashinken

28

Cập nhật:

Giải pháp của @Vikrant Chaudhary có vẻ tốt hơn:

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

Câu trả lời ban đầu của tôi:

xem liên kết này: Làm thế nào để bỏ qua các cuộc gọi lại ActiveRecord?

trong Rails3,

giả sử chúng ta có một định nghĩa lớp:

class User < ActiveRecord::Base
  after_save :generate_nick_name
end 

Cách tiếp cận1:

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

Cách tiếp cận2: Khi bạn muốn bỏ qua chúng trong các tệp rspec của bạn hoặc bất cứ điều gì, hãy thử điều này:

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

LƯU Ý: một khi điều này được thực hiện, nếu bạn không ở trong môi trường rspec, bạn nên đặt lại các cuộc gọi lại:

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

hoạt động tốt với tôi trên đường ray 3.0.5


20

đường ray 3:

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

11
Đẹp. Cũng MyModel.skip_callback (: tạo,: sau đó,: my_callback) để kiểm soát chính xác .. thấy ActiveSupport :: Callback :: ClassMethods tài liệu cho tất cả các lobang
tardate

4
Thông tin hữu ích: 'biểu tượng' reset_callbackskhông phải :after_save, mà là :save. apidock.com/rails/v3.0.9/ActiveSupport/Callbacks/ClassMethods/ khăn
nessur

19

Nếu mục tiêu chỉ đơn giản là chèn một bản ghi mà không cần gọi lại hoặc xác nhận hợp lệ và bạn muốn thực hiện nó mà không cần dùng đến đá quý bổ sung, thêm kiểm tra có điều kiện, sử dụng RAW SQL hoặc chuyển tiếp với mã thoát của bạn theo bất kỳ cách nào, hãy xem xét sử dụng "bóng" đối tượng "trỏ đến bảng db hiện tại của bạn. Thích như vậy:

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

Điều này hoạt động với mọi phiên bản của Rails, là chủ đề an toàn và loại bỏ hoàn toàn tất cả các xác nhận và gọi lại mà không sửa đổi mã hiện tại của bạn. Bạn chỉ có thể ném khai báo lớp đó ngay trước khi nhập thực tế của bạn và bạn nên đi. Chỉ cần nhớ sử dụng lớp mới của bạn để chèn đối tượng, như:

ImportedPerson.new( person_attributes )

4
Giải pháp tốt nhất EVER. Thanh lịch và đơn giản!
Rafael Oliveira

1
Điều này thực sự hiệu quả với tôi vì đó là điều tôi chỉ muốn làm trong thử nghiệm, để mô phỏng cơ sở dữ liệu "trước", mà không làm ô nhiễm đối tượng mô hình sản xuất của tôi với máy móc để tùy ý bỏ qua các cuộc gọi lại.
Douglas Lovell

1
Cho đến nay câu trả lời tốt nhất
robomc

1
Được nâng cấp bởi vì nó cho thấy cách khắc phục các ràng buộc đường ray hiện có và giúp tôi hiểu toàn bộ đối tượng MVC thực sự hoạt động như thế nào. Thật đơn giản và sạch sẽ.
Michael Schmitz

17

Bạn có thể thử một cái gì đó như thế này trong mô hình Người của bạn:

after_save :something_cool, :unless => :skip_callbacks

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

EDIT: after_save không phải là một biểu tượng, nhưng đó ít nhất là lần thứ 1.000 tôi đã cố gắng biến nó thành một biểu tượng.


1
Tôi thực sự nghĩ rằng đây là câu trả lời tốt nhất ở đây. Bằng cách này, logic xác định khi bỏ qua cuộc gọi lại có sẵn trong mô hình và bạn không có các đoạn mã điên ở khắp mọi nơi để lấy lại logic nghiệp vụ hoặc phá vỡ sự đóng gói send. KOODOS
Ziggy

10

Bạn có thể sử dụng update_columns:

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

Cập nhật các thuộc tính đã cho của một đối tượng, mà không gọi lưu, do đó bỏ qua các xác nhận và gọi lại.


7

Cách duy nhất để ngăn chặn tất cả các cuộc gọi lại after_save là có lần đầu tiên trả về false.

Có lẽ bạn có thể thử một cái gì đó như (chưa được kiểm tra):

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

1
Tôi thích thử (chưa được kiểm tra). Cảm giác hồi hộp.
Adamantish

Đã thử nghiệm và nó hoạt động. Tôi nghĩ rằng đây là một giải pháp rất tốt và sạch sẽ, cảm ơn!
thoát vị

5

Có vẻ như một cách để xử lý việc này trong Rails 2.3 (vì update_without_callbacks đã biến mất, v.v.), sẽ sử dụng update_all, một trong những phương pháp bỏ qua các cuộc gọi lại theo phần 12 của Hướng dẫn Rails để xác thực và gọi lại .

Ngoài ra, lưu ý rằng nếu bạn đang thực hiện một thao tác nào đó trong cuộc gọi lại after_ của mình, thì đó là một phép tính dựa trên nhiều liên kết (ví dụ: PGS has_many, trong đó bạn cũng thực hiện ac accept_nested_attribut_for), bạn sẽ cần phải tải lại liên kết, trong trường hợp là một phần của lưu , một trong những thành viên của nó đã bị xóa.


4

https://gist.github.com/576546

chỉ cần kết xuất bản vá khỉ này vào cấu hình / bộ khởi tạo / Skip_callbacks.rb

sau đó

Project.skip_callbacks { @project.save }

hoặc tương tự.

tất cả tín dụng cho tác giả


4

Nhất up-votedCâu trả lời có vẻ khó hiểu trong một số trường hợp.

Bạn có thể sử dụng chỉ một ifkiểm tra đơn giản nếu bạn muốn bỏ qua một cuộc gọi lại, như thế này:

after_save :set_title, if: -> { !new_record? && self.name_changed? }

3

Một giải pháp hoạt động trên tất cả các phiên bản Rails mà không cần sử dụng gem hay plugin chỉ đơn giản là phát hành các bản cập nhật trực tiếp. ví dụ

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

Điều này có thể (hoặc có thể không) là một tùy chọn tùy thuộc vào mức độ phức tạp của bản cập nhật của bạn. Điều này hoạt động tốt cho ví dụ cập nhật cờ trên một bản ghi từ trong một cuộc gọi lại after_save (mà không cần gọi lại cuộc gọi lại).


Không chắc chắn tại sao downvote, nhưng tôi vẫn nghĩ rằng câu trả lời trên là hợp pháp. Đôi khi, cách tốt nhất để tránh các sự cố với hành vi ActiveRecord là tránh sử dụng ActiveRecord.
Dave Smylie

Nâng cao trên nguyên tắc để chống lại -1. Chúng tôi chỉ có một vấn đề sản xuất (với một câu chuyện dài đằng sau nó) yêu cầu chúng tôi tạo ra một bản ghi mới (không phải bản cập nhật) và thực hiện các cuộc gọi lại sẽ rất thảm khốc. Tất cả các câu trả lời ở trên là hack cho dù họ có thừa nhận hay không và đi đến DB là giải pháp tốt nhất. Có điều kiện hợp pháp cho việc này. Mặc dù người ta nên cẩn thận với việc tiêm SQL với #{...}.
sinisterchipmunk

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

1

Không có điểm nào trong số các without_callbacksplugin này chỉ làm những gì bạn cần ...

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 hoạt động với Rails 2.x


1

Tôi đã viết một plugin thực hiện update_without_callbacks trong Rails 3:

http://github.com/dball/skip_activerecord_callbacks

Tôi nghĩ rằng giải pháp phù hợp là viết lại các mô hình của bạn để tránh các cuộc gọi lại ngay từ đầu, nhưng nếu điều đó không thực tế trong thời gian tới, plugin này có thể giúp ích.


1

Nếu bạn đang sử dụng Rails 2. Bạn có thể sử dụng truy vấn SQL để cập nhật cột của mình mà không cần chạy lại và xác nhận hợp lệ.

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

Tôi nghĩ rằng nó nên hoạt động trong bất kỳ phiên bản đường ray.


1

Khi tôi cần toàn quyền kiểm soát cuộc gọi lại, tôi tạo một thuộc tính khác được sử dụng làm công tắc. Đơn giản và hiệu quả:

Mô hình:

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

Kiểm tra:

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


1

Bạn có thể sử dụng đá quý lưu lén: https://rubygems.org/gems/sneaky-save .

Lưu ý điều này không thể giúp lưu các hiệp hội cùng mà không có xác nhận. Nó đưa ra lỗi 'created_at không thể null' vì nó trực tiếp chèn truy vấn sql không giống như một mô hình. Để thực hiện điều này, chúng ta cần cập nhật tất cả các cột db được tạo tự động.


1

Tôi cần một giải pháp cho Rails 4, vì vậy tôi đã đưa ra điều này:

ứng dụng / mô hình / mối quan tâm / 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

trong bất kỳ mô hình nào:

include SaveWithoutCallbacks

sau đó bạn có thể:

record.save_without_callbacks

hoặc là

Model::WithoutCallbacks.create(attributes)

0

Tại sao bạn muốn có thể làm điều này trong phát triển? Chắc chắn điều này có nghĩa là bạn đang xây dựng ứng dụng của mình với dữ liệu không hợp lệ và như vậy nó sẽ hoạt động kỳ lạ và không như bạn mong đợi trong sản xuất.

Nếu bạn muốn tạo dữ liệu dev db của mình với dữ liệu, cách tiếp cận tốt hơn là xây dựng một tác vụ cào sử dụng đá quý giả để xây dựng dữ liệu hợp lệ và nhập nó vào db tạo ra nhiều hoặc ít bản ghi như bạn muốn, nhưng nếu bạn là gót chân dựa vào nó và có một lý do chính đáng


Tôi không cố lưu mà không có xác nhận, chỉ cần không có cuộc gọi lại. Ứng dụng của tôi đang sử dụng các cuộc gọi lại để viết một số HTML tĩnh vào hệ thống tập tin (giống như một CMS). Tôi không muốn làm điều đó trong khi tải dữ liệu dev.
Ethan

Chỉ là một suy nghĩ, tôi đoán bất cứ khi nào trong quá khứ tôi đã thấy loại câu hỏi này, nó cố gắng giải quyết mọi thứ vì những lý do xấu.
giải mã

0

Một tùy chọn là có một mô hình riêng cho các thao tác đó, sử dụng cùng một bảng:

class NoCallbacksModel < ActiveRecord::Base
  set_table_name 'table_name_of_model_that_has_callbacks'

  include CommonModelMethods # if there are
  :
  :

end

(Cách tiếp cận tương tự có thể làm cho mọi thứ dễ dàng hơn để bỏ qua xác nhận)

Stephan


0

Một cách khác là sử dụng móc xác nhận thay vì gọi lại. Ví dụ:

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

Bằng cách đó, bạn có thể lấy do_s Something theo mặc định, nhưng bạn có thể dễ dàng ghi đè lên nó bằng:

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

3
Đây có vẻ là một ý tưởng tồi - bạn nên sử dụng những thứ cho mục đích dự định của họ. Điều cuối cùng bạn muốn là xác nhận của bạn có tác dụng phụ.
chug2k

0

Một cái gì đó nên hoạt động với tất cả các phiên bản ActiveRecordmà không phụ thuộc vào các tùy chọn hoặc phương thức activerecord có thể tồn tại hoặc không tồn tại.

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: sử dụng "mô hình Activerecord khác" trên cùng một bảng


0

Đối với các cuộc gọi lại tùy chỉnh, sử dụng một attr_accessorvà một unlesstrong cuộc gọi lại.

Xác định mô hình của bạn như sau:

class Person << ActiveRecord::Base

  attr_accessor :skip_after_save_callbacks

  after_save :do_something, unless: :skip_after_save_callbacks

end

Và sau đó nếu bạn cần lưu bản ghi mà không cần nhấn các after_savecuộc gọi lại mà bạn đã xác định, hãy đặt skip_after_save_callbacksthuộc tính ảo thành true.

person.skip_after_save_callbacks #=> nil
person.save # By default, this *will* call `do_something` after saving.

person.skip_after_save_callbacks = true
person.save # This *will not* call `do_something` after saving.

person.skip_after_save_callbacks = nil # Always good to return this value back to its default so you don't accidentally skip callbacks.

-5

Không phải là cách sạch nhất, nhưng bạn có thể bọc mã gọi lại trong điều kiện kiểm tra môi trường Rails.

if Rails.env == 'production'
  ...
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.