Extending hash constant in another file - ruby-on-rails

I've got a gem where in one of classes is sth similiar:
class Test
TESTING = {
:sth1 => 'foo',
:sth2 => 'bar'
}
# p Test.new.show
# should print 'cat'
def show
p TESTING[:sth3]
end
end
I extended in other file
# in other file
class Test
TESTING = {
:sth3 => 'cat'
}
end
But i need to use :sth3 in first file, as the first part of code stands.
Thx in advance.

You didn't extend it, you replaced the hash with a new one. Here's how to fix it:
# in the other file
Test::TESTING[:sth3] = 'cat'
I recommend using methods with lazy initialization, so that you can arrange the assignments in any order:
class Test
def self.testing
#testing ||= {}
end
testing[:sth1] = 'foo'
testing[:sth2] = 'bar'
end
# in the other file
Test.testing[:sth3] = 'cat'

Related

Minitest::Mock#expect without specific order?

Suppose I have a class:
class Foo
def process
MyModel.where(id: [1,3,5,7]).each do |my_model|
ExternalService.dispatch(my_modal.id)
end
end
end
I want to test it:
class FooTest < ActiveSupport::TestCase
def process_test
external_service_mock = MiniTest::Mock.new
[1,3,5,7].each do |id|
external_service_mock.expect(:call, true, id)
end
ExternalService.stub(:dispatch, events_mock) do
Foo.new.process
end
external_service_mock.verify
end
end
However, #expect enforces that the following calls are made in the same order as #expect was called. That's not good for me, because I have no confidence, in what order will the results be returned by the DB.
How can I solve this problem? Is there a way to expect calls without specific order?
Try Using a Set
require 'set'
xs = Set[3,5,7,9]
#cat = Minitest::Mock.new
#cat.expect :meow?, true, [xs]
#cat.meow? 7 # => ok...
#cat.expect :meow?, true, [xs]
#cat.meow? 4 # => boom!
Alternatively, a less specific option:
Given that the value returned by the mock isn't a function of the parameter value, perhaps you can just specify a class for the parameter when setting up your mock. Here's an example of a cat that expects meow? to be called four times with an arbitrary integer.
#cat = Minitest::Mock.new
4.times { #cat.expect(:meow?, true, [Integer]) }
# Yep, I can meow thrice.
#cat.meow? 3 # => true
# Mope, I can't meow a potato number of times.
#cat.meow? "potato" # => MockExpectationError

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

RSpec testing model method

I have this method in my models/images.rb model. I am starting with testing and having a hard time coming up with tests for it. Would appreciate your help.
def self.tags
t = "db/data.csv"
#arr = []
csvdata = CSV.read(t)
csvdata.shift
csvdata.each do |row|
row.each_with_index do |l, i|
unless l.nil?
#arr << l
end
end
end
#arr
end
First off a word of advice - CSV is probably the worst imaginable data format and is best avoided unless absolutely unavoidable - like if the client insists that manipulating data in MS Excel is a good idea (it is not).
If you have to use CSV don't use a method name like .tags which can confused for a regular ActiveRecord relation.
Testing methods that read from the file system can be quite difficult.
To start with you might want to alter the signature of the method so that you can pass a file path.
def self.tags(file = "db/data.csv")
# ...
end
That way you can pass a fixture file so that you can test it deterministically.
RSpec.describe Image do
describe "tags" do
let(:file) { Rails.root.join('spec', 'support', 'fixtures', 'tags.csv') }
it 'returns an array' do
expect(Image.tags(file)).to eq [ { foo: 'bar' }, { foo: 'baz' } ]
end
end
end
However your method is very ideosyncratic -
def self.tags
t = "db/data.csv"
#arr = []
self.tags makes it a class method yet you are declaring #arr as an instance variable.
Additionally Ruby's enumerable module provides so many methods for manipulating arrays that using an outer variable in a loop is not needed.
def self.tags(file = "db/data.csv")
csv_data = CSV.read(file)
csv_data.shift
csv_data.compact # removes nil elements
end

How to generate data in one method and pass that data into another method but in the same class

How do I generate data in one method and pass that data into another method but in the same class?
I have a Ruby class with two methods. Is there any way to call create_data_hash and return the results as two variables into rest_call?
I also need to be able to call the methods create_data_hash.email and return "foo#foo.com", and create_data_hash.password and return "strongpassword".
I need to be able to use those values in other parts of the program, but yet still need this class to handle the generation of the data.
require 'json'
module New
class Generator
def create_data_hash
email = 'foo#foo.com'
password = 'strongpassword'
end
def rest_call(user_email, user_password)
data_hash = { email: user_email,
password: user_password ,
info: "user",
name: "JohnDoe",
}
#random = endpoint_tester_class.new
#random.endpoint_test(data_hash.to_json)
end
end
end
This is possible with the next technique.
def accept_multi(*args)
puts "args are: #{args}"
puts "args class is #{args.class}"
end
def accept_two(one, two)
puts "first arg is #{one}", "second arg is #{two}"
end
def return_two
return "a", "b"
end
# now run the code
accept_multi return_two
# prints:
# args are: [["a", "b"]]
# args class is Array
# do not forget '*' symbol
accept_two *return_two
# prints:
# first arg is a
# second arg is b
return_two.class
# prints
# Array
NOTE: if you will use it, do not forget to check arity of your methods. For example, if you call accept_two *[1, 2, 3], ArgumentError exception will be raised.
Also, you are able to use instance variables.
class TestClass
def set_vars
#one = 1
#two = 2
end
def print_vars
puts #one, #two
end
def process
set_vars
print_vars
end
end
tc = TestClass.new
tc.process

em-mongo examples?

Looking to use em-mongo for a text analyzer script which loads text from db, analyzes it, flags keywords and updates the db.
Would love to see some examples of em-mongo in action. Only one I could find was on github em-mongo repo.
require 'em-mongo'
EM.run do
db = EM::Mongo::Connection.new.db('db')
collection = db.collection('test')
EM.next_tick do
doc = {"hello" => "world"}
id = collection.insert(doc)
collection.find('_id' => id]) do |res|
puts res.inspect
EM.stop
end
collection.remove(doc)
end
end
You don't need the next_tick method, that is em-mongo doing for you. Define callbacks, that are executed if the db actions are done. Here is a skeleton:
class NonBlockingFetcher
include MongoConfig
def initialize
configure
#connection = EM::Mongo::Connection.new(#server, #port)
#collection = init_collection(#connection)
end
def fetch(value)
mongo_cursor = #collection.find({KEY => value.to_s})
response = mongo_cursor.defer_as_a
response.callback do |documents|
# foo
# get one document
doc = documents.first
end
response.errback do |err|
# foo
end
end
end

Resources