我需要知道在块内调用的记录属性(比如我需要类似下面的内容):
def my_custom_method(&block)
some_method_that_starts_tracking
block.call
some_method_that_stops_tracking
puts some_method_that_returns_called_records_attributes
do_something_about(some_method_that_returns_called_records_attributes)
end
my_custom_method { somecodethatcallsauthorofbook1andemailandfirstnameofuser43 }
# this is the `puts` output above (just as an example)
# => {
# #<Book id:1...> => [:author],
# #<User id:43...> => [:email, :first_name]
# }
块内的代码可以是任何
ApplicationRecord
的子类的任何实例,因此它可以是Book
,User
等任何模型的实例...... 根据我的理解,这类似于rspec
在预期调用方法时的工作方式。它以某种方式跟踪该方法的任何调用。所以,我最初的尝试是做类似以下的事情(这还没有完全奏效):
def my_custom_method(&block)
called_records_attributes = {}
ApplicationRecord.descendants.each do |klass|
klass.class_eval do
attribute_names.each do |attribute_name|
define_method(attribute_name) do
called_records_attributes[self] ||= []
called_records_attributes[self] << attribute_name
self[attribute_name]
end
end
end
end
block.call
# the above code will work but at this point, I don't know how to clean the methods that were defined above, as the above define_methods should only be temporary
puts called_records_attributes
end
my_custom_method { Book.find_by(id: 1).title }
# => {
# #<Book id: 1...> => ['title']
# }
.descendants
可能不是一个好主意,因为如果我没弄错的话,Rails会使用autoload
我正在编写一个gem live_record,我正在添加一个新功能,允许开发人员只需编写类似
的内容<!-- app/views/application.html.erb -->
<body>
<%= live_record_sync { @book.some_custom_method_about_book } %>
</body>
...将在页面上呈现@book.some_custom_method_about_book
,但同时live_record_sync
包装器方法会记录在该块内调用的所有属性(即在some_custom_method_about_book
内调用@book.title
,然后将这些属性设置为块自己的“依赖关系”,稍后当该特定书籍的属性更新时,我也可以直接更新HTML该属性是“依赖”的页面,如上所述。我知道这不是一个准确的解决方案,但我想先通过实验来开辟我的机会。
- Rails 5
答案 0 :(得分:0)
免责声明:我认为这只是一个平庸的解决方案,但希望可以帮助任何有同样问题的人。
我尝试阅读 rspec 源代码,但因为我无法轻易理解幕后发生的事情,而且我发现 rspec & #39; s(ie)expect(Book.first).to receive(:title)
与我真正想要的不同,因为已经指定了方法(即:title
),而我想要的是跟踪任何属性的方法,因为在这两个原因中,我跳过了进一步的阅读,并尝试了我自己的解决方案,希望以某种方式工作;见下文。
请注意,我在这里使用Thread
local-storage,因此此代码应该是线程安全的(尚未测试)。
# lib/my_tracker.rb
class MyTracker
Thread.current[:my_tracker_current_tracked_records] = {}
attr_accessor :tracked_records
class << self
def add_to_tracked_records(record, attribute_name)
Thread.current[:my_tracker_current_tracked_records][{model: record.class.name.to_sym, record_id: record.id}] ||= []
Thread.current[:my_tracker_current_tracked_records][{model: record.class.name.to_sym, record_id: record.id}] << attribute_name
end
end
def initialize(block)
@block = block
end
def call_block_while_tracking_records
start_tracking
@block_evaluated_value = @block.call
@tracked_records = Thread.current[:my_tracker_current_tracked_records]
stop_tracking
end
def to_s
@block_evaluated_value
end
# because I am tracking record-attributes, and you might want to track a different object / method, then you'll need to write your own `prepend` extension (look for how to use `prepend` in ruby)
module ActiveRecordExtensions
def _read_attribute(attribute_name)
if Thread.current[:my_tracker_current_tracked_records] && !Thread.current[:my_tracker_is_tracking_locked] && self.class < ApplicationRecord
# I added this "lock" to prevent infinite loop inside `add_to_tracked_records` as I am calling the record.id there, which is then calling this _read_attribute, and then loops.
Thread.current[:my_tracker_is_tracking_locked] = true
::MyTracker.add_to_tracked_records(self, attribute_name)
Thread.current[:my_tracker_is_tracking_locked] = false
end
super(attribute_name)
end
end
module Helpers
def track_records(&block)
my_tracker = MyTracker.new(block)
my_tracker.call_block_while_tracking_records
my_tracker
end
end
private
def start_tracking
Thread.current[:my_tracker_current_tracked_records] = {}
end
def stop_tracking
Thread.current[:my_tracker_current_tracked_records] = nil
end
end
ActiveSupport.on_load(:active_record) do
prepend MyTracker::ActiveRecordExtensions
end
ActiveSupport.on_load(:action_view) do
include MyTracker::Helpers
end
ActiveSupport.on_load(:action_controller) do
include MyTracker::Helpers
end
<强> some_controller.rb 强>
book = Book.find_by(id: 1)
user = User.find_by(id: 43)
my_tracker = track_records do
book.title
if user.created_at == book.created_at
puts 'same date'
end
'thisisthelastlineofthisblockandthereforewillbereturned'
end
puts my_tracker.class
# => #<MyTracker ... >
puts my_tracker.tracked_records
# => {
# {model: :Book, record_id: 1} => ['title', 'created_at'],
# {model: :User, record_id: 43} => ['created_at']
# }
puts my_tracker
# => 'thisisthelastlineofthisblockandthereforewillbereturned'
# notice that `puts my_tracker` above prints out the block itself
# this is because I defined `.to_s` above.
# I need this `.to_s` so I can immediately print the block as-is in the views.
# see example below
<强> some_view.html.erb 强>
<%= track_records { current_user.email } %>
P.S。也许我把它作为一个宝石包装起来会更好。如果您有兴趣,请告诉我