RSpec "change all" matcher - ruby-on-rails

I've got some job that updates records, and I want something like:
it 'updates each record' do
expect {
described_class.perform_now
}.to(change_all_of{
Record.pluck(:updated_at)
})
end
Only I can't find anything that looks like how to accomplish this, or what I can recognize as the docs for how to write a custom matcher.
The issue is that change, on an array, will return true if any element has changed; I only want to return true if every element has changed.
Can anyone point me either at whatever I missed that would let me do this, OR, whatever docs/info I need to write my own change matcher?

Alright, thanks to this answer on another question, and staring at the actual code, here's what I've got -
module RSpec
module Matchers
def change_all &block
BuiltIn::ChangeAll.new(nil, nil, &block)
end
module BuiltIn
class ChangeAll < Change
def initialize receiver=nil, message=nil, &block
#change_details = ChangeAllDetails.new(receiver, message, &block)
end
def failure_message
"expected all elements to change, but " + (
#change_details.actual_after & #change_details.actual_before
).collect do |unchanged|
"before[#{#change_details.actual_before.find_index(unchanged)}] " \
"after[#{#change_details.actual_after.find_index(unchanged)}] " \
"#{unchanged}"
end.join(",") + " remained the same"
end
end
class ChangeAllDetails < ChangeDetails
attr_accessor :actual_before
def changed?
!(#actual_after & #actual_before).any?
end
end
end
end
end
Suggestions welcome!

Related

Ruby/Rails, Gate clause syntax to enforce security policy

I have a difficult problem to solve.
I would like to replicate, the title_with_if_statement methods results, but using the syntax used in the title method. However, I do not know what to put in the enforce_policy_to_level() method to accomplish this.
I can do 'title_less_readable', but I find the title method much more readable since the policy is right at the top, and since I will be doing this for hundreds of methods, I want to keep it DRY and readable.
Any advice?
Is this even possible? It's basically a before_action but uses the method's own results.
class Car
def title
enforce_policy_to_level(1)
# Code that returns the "Car Title" string
"Car Title"
end
def title_less_readable
# Code that returns "Car Title" string
content = "Car Title"
return secure_text(content) unless authorized_to_level?(1)
content
end
def title_with_if_statement
# Code that returns the "Car Title" string
content = "Car Title"
if authorized_to_level?(1)
content
else
secure_text(content)
end
end
private
def enforce_policy_to_level(level)
# Return from title method with the secured result of title method
# if it's not authorized. In this example it would be secure_text('Car Title')
# which output return '---------'
# If if authorized, just continue with how title method would normally return
end
def authorized_to_level?(level)
current_user_level = 1 # Dynamic from user record
current_user_level >= level
end
def secure_text(text)
'X' * text.length
end
end
Why not just something like this?
def title
enforce_policy_to_level(1) do
"Car Title"
end
end
private
def enforce_policy_to_level(level, &block)
if authorized_to_level?(level)
secure_txt yield
else
yield
end
end
Consider separating your concerns: I'd approach this by creating a separate function that handles the authorization.
module AuthorizedText
module_function
def secure_text(text, authorized)
return authorized ? text : 'X' * text.length
end
end
Call this in Car like this:
AuthorizedText.secure_text("Car Title", authorized_to_level(1))
You can shorthand this with a convenience method:
def secure(level, text)
AuthorizedText.secure_text(text, authorized_to_level(level))
end
so that your car_title method would look like
def car_title
secure(1, "Car Title")
end
You could generalize a method wrapper by defining a class method that would let you do something like
def car_title
"Car Title"
end
secure :car_title
But I'd call this over-engineered and ultimately less readable to anyone who isn't you (including you 8 months from now, likely!). I'd avoid trying to be fancy like this, as fun as it may be.
Also, if current_user and current_user_level make sense outside the context of Car and you may want to reuse this elsewhere, you could go further by moving the auth checking methods into AuthorizedText, if that's appropriate:
module AuthorizedText
module_function
def secure_text(text, user, level = 0)
return authorized?(user, level) ? text : 'X' * text.length
end
def authorized?(user, level)
# for example; I don't know what your user object looks like
user.level >= level
end
end

How to track objects "called" inside a block?

Question:
I need to know the records' attributes that have been called inside a block (say I need something like the following):
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]
# }
code inside the block can be anything
Specifically, I meant to track any instance of a subclass of ApplicationRecord, so it can be instance of any models like Book, User, etc...
Attempts:
From my understanding, this is similar to how rspec works when a method is expected to be called. That it somehow tracks any calls of that method. So, my initial attempt is to do something like the following (which does not yet fully work):
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']
# }
the .descendants above probably is not a good idea because Rails use autoload if I'm not mistaken
as already said above in the comment, I do not know how to remove these "defined_methods" that are just supposed to be only temporary for the duration of this "block".
furthermore, my code above would probably have overriden the "actual" attribute getters of the models, if ever any has been already defined, which is bad.
Background:
I am writing a gem live_record which I am adding a new feature that will allow a developer to just simply write something like
<!-- app/views/application.html.erb -->
<body>
<%= live_record_sync { #book.some_custom_method_about_book } %>
</body>
... which will render #book.some_custom_method_about_book as-is on the page, but at the same time the live_record_sync wrapper method would take note of all the attributes that have been called inside that block (i.e. inside some_custom_method_about_book the #book.title is called), and then it sets these attributes as the block's own "dependencies", in which later when that specific book's attribute has been updated, I can already also update directly the HTML page of which this attribute is a "dependency" as like specified just above. I am aware that this is not an accurate solution, but I'd like to open up my chances by experimenting on this first.
-- Rails 5
Disclaimer: I believe this is just a mediocre solution, but hopefully helps anyone with the same problem.
I tried reading rspec source code, but because I couldn't easily comprehend what is happening under the hood, and that it occurred to me that rspec's (i.e.) expect(Book.first).to receive(:title) is different from what I really want because the methods there are already specified (i.e. :title), while what I want is to track ANY methods that are attributes, so because of these two reasons I skipped reading further, and attempted my own solution, which hopefully did somehow work; see below.
Note that I am using Thread local-storage here, so this code should be thread-safe (untested yet).
# 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
Usage Example
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. Maybe it's better that I wrap this up as a gem. If you're interested, let me know

How to decouple functionality and logging in a ruby method

I like to log a lot. In my Rails app I have a lot of methods like:
def my_method(argument1:, argument2:)
logger.info "Starting my_method with arguments: #{argument1} and #{argument2}"
result = argument1 + argument2
logger.info "Finished my_method with result: #{result}"
end
How to decouple the functionality and the logging of the methods?.
Ideally the result would look something like this (borrowing the callback concept from Rails just as an example):
before_method: :my_method_log_start, only: :my_method
after_method: :my_method_log_end, only: :my_method
def my_method(argument1:, argument2:)
result = argument1 + argument2
end
private
def my_method_log_start
logger.info "Starting my_method with arguments: #{argument1} and #{argument2}"
end
def my_method_log_end
logger.info "Finished my_method with result: #{result}"
end
I know this is less efficient in terms of lines of code, it is more readable (in my opinion).
I have read about Aspect Orient Programming and some of their gems like Aquarius, but looks like an overkill to add a new paradigm just for logging.
I think Avdi Grimm has a good explanation of the technique you could use. The idea is to extract logging (or anything else) to the listener class and publish events to that listener, basic example would be
class Task
# ...
def add_listener(listener)
(#listeners ||= []) << listener
end
# ...
def notify_listeners(event_name, *args)
#listeners && #listeners.each do |listener|
if listener.respond_to?(event_name)
listener.public_send(event_name, self, *args)
end
end
end
end
and do sth like
task = Task.new
task.add_lestener(YourLoggerClass.new)
task.notify_listeners(:start_logging)
task.notify_listeners(:end_logging)
If this is only for local debugging, it is the good use case for TracePoint class. Here is the code:
tp1 = TracePoint.new do |tp|
if tp.event == :call
method = tp.defined_class.method(tp.method_id)
arguments = method.parameters.map do |param|
"#{param[1]}: #{tp.binding.local_variable_get(param[1])}"
end.join(", ")
puts "Starting #{tp.method_id} with arguments #{arguments}"
elsif tp.event.to_s == "return"
puts "Finished #{tp.method_id} with result: #{tp.return_value}"
end
end
tp1.enable
def my_method1(a, b)
a + b
end
puts my_method1(2, 3)
I recommend reading the documentation for this class, it has really nice features. Of course you need to polish this code a little bit to handle some edge cases. You can add some filter to only invoke tracing block for methods that you care about. Or you can enable/disable this based on some parts of the code.
You can call method by it's name, or turn it to proc and pass to another method. So you can write something like that:
def foo(a, b)
a + b
end
def call_with_logging(method_name, *args)
args_as_string = args.map(&:to_s).join(' ')
puts "Starting my_method with arguments #{args_as_string}"
result = Object.send(method_name, *args)
puts "Finished my_method with result: #{result}"
end
call_with_logging :foo, 1, 2

Rails 5 - iterate until field matches regex

In my app that I am building to learn Rails and Ruby, I have below iteration/loop which is not functioning as it should.
What am I trying to achieve?
I am trying to find the business partner (within only the active once (uses a scope)) where the value of the field business_partner.bank_account is contained in the field self_extracted_data and then set the business partner found as self.sender (self here is a Document).
So once a match is found, I want to end the loop. A case exists where no match is found and sender = nil so a user needs to set it manually.
What happens now, is that on which ever record of the object I save (it is called as a callback before_save), it uses the last identified business partner as sender and the method does not execute again.
Current code:
def set_sender
BusinessPartner.active.where.not(id: self.receiver_id).each do |business_partner|
bp_bank_account = business_partner.bank_account.gsub(/\s+/, '')
rgx = /(?<!\w)(#{Regexp.escape(bp_bank_account)})?(?!\‌​w)/
if self.extracted_data.gsub(/\s+/, '') =~ rgx
self.sender = business_partner
else
self.sender = nil
end
end
end
Thanks for helping me understand how to do this kind of case.
p.s. have the pickaxe book here yet this is so much that some help / guidance would be great. The regex works.
Using feedback from #moveson, this code works:
def match_with_extracted_data?(rgx_to_match)
extracted_data.gsub(/\s+/, '') =~ rgx_to_match
end
def set_sender
self.sender_id = matching_business_partner.try(:id) #unless self.sender.id.present? # Returns nil if no matching_business_partner exists
end
def matching_business_partner
BusinessPartner.active.excluding_receiver(receiver_id).find { |business_partner| sender_matches?(business_partner) }
end
def sender_matches?(business_partner)
rgx_registrations = /(#{Regexp.escape(business_partner.bank_account.gsub(/\s+/, ''))})|(#{Regexp.escape(business_partner.registration.gsub(/\s+/, ''))})|(#{Regexp.escape(business_partner.vat_id.gsub(/\s+/, ''))})/
match_with_extracted_data?(rgx_registrations)
end
In Ruby you generally want to avoid loops and #each and long, procedural methods in favor of Enumerable iterators like #map, #find, and #select, and short, descriptive methods that each do a single job. Without knowing more about your project I can't be sure exactly what will work, but I think you want something like this:
# /models/document.rb
class Document < ActiveRecord::Base
def set_sender
self.sender = matching_business_partner.try(:id) || BusinessPartner.active.default.id
end
def matching_business_partners
other_business_partners.select { |business_partner| account_matches?(business_partner) }
end
def matching_business_partner
matching_business_partners.first
end
def other_business_partners
BusinessPartner.excluding_receiver_id(receiver_id)
end
def account_matches?(business_partner)
rgx = /(?<!\w)(#{Regexp.escape(business_partner.stripped_bank_account)})?(?!\‌​w)/
data_matches_bank_account?(rgx)
end
def data_matches_bank_account?(rgx)
extracted_data.gsub(/\s+/, '') =~ rgx
end
end
# /models/business_partner.rb
class BusinessPartner < ActiveRecord::Base
scope :excluding_receiver_id, -> (receiver_id) { where.not(id: receiver_id) }
def stripped_bank_account
bank_account.gsub(/\s+/, '')
end
end
Note that I am assigning an integer id, rather than an ActiveRecord object, to self.sender. I think that's what you want.
I didn't try to mess with the database relations here, but it does seem like Document could include a belongs_to :business_partner, which would give you the benefit of Rails methods to help you find one from the other.
EDIT: Added Document#matching_business_partners method and changed Document#set_sender method to return nil if no matching_business_partner exists.
EDIT: Added BusinessPartner.active.default.id as the return value if no matching_business_partner exists.

Many very similar functions, spaghetti code fix?

I have approx 11 functions that look like this:
def pending_acceptance(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
pending_acceptance?; collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
def pending_start(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
pending_start?; collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
The iteration is always the same, but next unless conditions are different. In case you wonder: it's next unless and ; in it because RuboCop was complaining about it. Is there a solution to implement it better? I hate this spaghetti code. Something like passing the condition into "iterate_it" function or so...
edit: Cannot just pass another parameter because the conditions are double sometimes:
def picked_up(order_fulfillments)
order_fulfillments.each do |order_fulfillment|
next unless
order_fulfillment.handed_over_late? && order_fulfillment.
fulfillment_time_calculator.pending_handover?
collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
edit2: One question yet: how could I slice a symbol, to get a user role from a status? Something like:
:deliverer_started => :deliverer or 'deliverer'?
You can pass another parameter when you use that parameter to decide what condition to check. Just store all possible conditions as lambdas in a hash:
FULFILLMENT_ACTIONS = {
pending_acceptance: lambda { |fulfillment| fulfillment.fulfillment_time_calculator.pending_acceptance? },
pending_start: lambda { |fulfillment| fulfillment.fulfillment_time_calculator.pending_acceptance? },
picked_up: lambda { |fulfillment| fulfillment.handed_over_late? && fulfillment.fulfillment_time_calculator.pending_handover? }
}
def process_fulfillments(type, order_fulfillments)
condition = FULFILLMENT_ACTIONS.fetch(type)
order_fulfillments.each do |order_fulfillment|
next unless condition.call(order_fulfillment)
collect_fulfillments(order_fulfillment.status, order_fulfillment)
end
end
To be called like:
process_fulfillments(:pending_acceptance, order_fulfillments)
process_fulfillments(:pending_start, order_fulfillments)
process_fulfillments(:picked_up, order_fulfillments)
you can make array of strings
arr = ['acceptance','start', ...]
in next step:
arr.each do |method|
define_method ( 'pending_#{method}'.to_sym ) do |order_fulfillments|
order_fulfillments.each do |order_fulfillment|
next unless order_fulfillment.fulfillment_time_calculator.
send('pending_#{method}?'); collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
end
for more information about define_method
While next is handy it comes late(r) in the code and is thus a bit more difficult to grasp. I would first select on the list, then do the action. (Note that this is only possible if your 'check' does not have side effects like in order_fullfillment.send_email_and_return_false_if_fails).
So if tests can be complex I would start the refactoring by expressing the selection criteria and then pulling out the processing of these items (wich also matches more the method names you have given), somewhere in the middle it might look like this:
def pending_acceptance(order_fulfillments)
order_fulfillments.select do |o|
o.fulfillment_time_calculator.pending_acceptance?
end
end
def picked_up(order_fulfillments)
order_fulfillments.select do |order_fulfillment|
order_fulfillment.handed_over_late? && order_fulfillment.
fulfillment_time_calculator.pending_handover?
end
end
def calling_code
# order_fulfillments = OrderFulFillments.get_from_somewhere
# Now, filter
collect_fulfillments(pending_start order_fulfillments)
collect_fulfillments(picked_up order_fulfillments)
end
def collect_fullfillments order_fulfillments
order_fulfillments.each {|of| collect_fullfillment(of) }
end
You'll still have 11 (+1) methods, but imho you express more what you are up to - and your colleagues will grok what happens fast, too. Given your example and question I think you should aim for a simple, expressive solution. If you are more "hardcore", use the more functional lambda approach given in the other solutions. Also, note that these approaches could be combined (by passing an iterator).
You could use something like method_missing.
At the bottom of your class, put something like this:
def order_fulfillment_check(method, order_fulfillment)
case method
when "picked_up" then return order_fulfillment.handed_over_late? && order_fulfillment.fulfillment_time_calculator.pending_handover?
...
... [more case statements] ...
...
else return order_fulfillment.fulfillment_time_calculator.send(method + "?")
end
end
def method_missing(method_name, args*, &block)
args[0].each do |order_fulfillment|
next unless order_fulfillment_check(method_name, order_fulfillment);
collect_fulfillments(
order_fulfillment.status,
order_fulfillment
)
end
end
Depending on your requirements, you could check if the method_name starts with "pending_".
Please note, this code is untested, but it should be somewhere along the line.
Also, as a sidenote, order_fulfillment.fulfillment_time_calculator.some_random_method is actually a violation of the law of demeter. You might want to adress this.

Resources