In scheduler.rake I have this method:
task :email_unaccepted_meetings => :environment do
desc "If meeting time is less than 3 days away, and not accepted, send email to requestee"
#date_plus_three_days = Time.now + 3.days
#meetings = Meeting.where("status = ? AND accepted != ? AND meeting_time < ? ", "Active", true, #date_plus_three_days)
if #meetings != nil
#meetings.each do |meeting|
EmailAndPushHelper.unaccepted_lex_request(meeting.id)
end
end
end
and in /controllers/concerns/email_and_push_helper.rb I have this:
module EmailAndPushHelper
def unaccepted_lex_request(meeting_id)
puts meeting_id
#meeting = Meeting.find(meeting_id)
if(check_settings(#meeting.requestee_id, 'lex_email') == true)
MeetingMailer.unaccepted_mtg_request(#meeting).deliver_later
end
if(check_settings(#meeting.requestee_id, 'lex_push'))
#title = "A Lex request needs your response."
#message = "#{#meeting.requestor.preferred_name} is waiting for your response.'"
#data = {
"status": "ok",
"body": #message,
"title": #title,
"meetingId": #meeting.id
}
PushNotificationWorker.perform_async(#meeting.requestee.id, #title, #message, #data)
end
end
end
When I call the task email_unaccepted_meetings I get NoMethodError (undefined method 'unaccepted_lex_request' for EmailAndPushHelper:Module)
In other rake tasks I am calling Mailer methods, Worker methods, so I'm not totally understanding what is going on here. I've tried requiring the file in scheduler.rake but it gives the same error, which makes me believe that it's accessing the file /controllers/concerns/email_and_push_helper.rb, but can not find the method unaccepted_lex_request for some reason?
Thanks in advance for your help!
To declare module methods you need to define the method with self.
module EmailAndPushHelper
def self.unaccepted_lex_request(meeting_id)
# ...
end
end
Instance methods of a module are only available when you include or extend something else with the module. Not as a method of the module itself.
But this really the wrong place/name for this code. controller/concerns is really where you put mixins that are mixed into your controller. Neither is this really a helper which is also an extremely vague term that also generally means a mixin thats used in the view and sometimes the controller in Rails.
This code actually belongs in a job or a service object.
class EmailAndPushJob < ApplicationJob
queue_as :default
def perform(meeting)
# You will have to solve the NoMethodError here
if check_settings(meeting.requestee_id, 'lex_email')
MeetingMailer.unaccepted_mtg_request(meeting).deliver_later
end
if check_settings(meeting.requestee_id, 'lex_push')
title = "A Lex request needs your response."
message = "#{meeting.requestor.preferred_name} is waiting for your response.'"
PushNotificationWorker.perform_async(meeting.requestee.id, title, message, {
# Use strings or symbols!
"status" => "ok",
"body" => message,
"title" => title,
"meetingId" => meeting.id
})
end
end
end
You also want to use batches in your rake task instead of just .each as that will load every record into memory and crash your server once you have a decent amount of data.
task :email_unaccepted_meetings => :environment do
desc "If meeting time is less than 3 days away, and not accepted, send email to requestee"
meetings = Meeting.where(
status: "Active",
accepted: true,
meeting_time: Time.now..3.days.from_now
).find_each do |meeting|
EmailAndPushJob.perform_now(meeting)
end
end
I solved by adding this to the top of scheduler.rake
require "#{Rails.root}/app/controllers/concerns/email_and_push_helper.rb"
include EmailAndPushHelper
And calling it like this:
`EmailAndPushHelper::unaccepted_lex_request(meeting.id)`
Related
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
I need to mark a collection of messages at the background (I am using delayed_job gem) since it takes some time on the foreground. So I've created an ActiveJob class MarkMessagesAsReadJob, and passed it user and messages variables in order to mark all of the messages read for user.
// passing the values in the controller
#messages = #conversation.messages
MarkMessagesAsReadJob.perform_later(current_user, #messages)
and in my ActiveJob class, I perform the task.
// MarkMessagesAsReadJob.rb
class MarkMessagesAsReadJob < ActiveJob::Base
queue_as :default
def perform(user, messages)
messages.mark_as_read! :all, :for => user
end
end
However, when I tried to perform the task, I got the error
ActiveJob::SerializationError (Unsupported argument type: ActiveRecord::Associations::CollectionProxy):
I read that we can only pass supported types to the ActiveJob, and I think it can not serialize the CollectionProxy object. How can I workaround/fix this?
PS: I considered
#messages.map { |message| MarkMessagesAsReadJob.perform_later(current_user, message) }
however I think marking them one by one is pretty expensive .
I think the easy way is pass message ids to the perform_later() method, for example:
in controller:
#messages = #conversation.messages
message_ids = #messages.pluck(:id)
MarkMessagesAsReadJob.perform_later(current_user, message_ids)
And use it in ActiveJob:
def perform(user, message_ids)
messages = Message.where(id: message_ids)
messages.mark_as_read! :all, :for => user
end
For larger data sets, passing the message_ids is impractical. Instead, pass the SQL for the messages:
#messages = #conversation.messages
MarkMessagesAsReadJob.perform_later(current_user, #messages.to_sql)
then query them from the job:
class MarkMessagesAsReadJob < ActiveJob::Base
queue_as :default
def perform(user, messages_sql)
messages = Message.find_by_sql(messages_sql)
messages.mark_as_read! :all, :for => user
end
end
Because the job will be executed later, I think we should pass ids as parameter instead a collection
ActiveJob need serialize parameter, and SerializationError will be thrown if parameter type isn't supported
http://api.rubyonrails.org/classes/ActiveJob/SerializationError.html
ex:
#message_ids = #conversation.messages.pluck(:id)
# use string if array is not supported
# #message_ids = #message_ids.join(", ")
MarkMessagesAsReadJob.perform_later(current_user, #message_ids)
then query those messages again and mark it
class MarkMessagesAsReadJob < ActiveJob::Base
queue_as :default
def perform(user, message_ids)
# use string if array is not supported
# message_ids = message_ids.split(",").map(&:to_i)
messages = Message.where(id: message_ids) #change this to something else
messages.mark_as_read! :all, :for => user
end
end
not tested , hope that it will be ok
I have the following code:
list_entities = [{:phone => '0000000000', :name => 'Test', :"#i:type => '1'},{:phone => '1111111111', :name => 'Demo', :"#i:type => '1'}]
list_entities.each do |list_entity|
phone_contact = PhoneContact.create(list_entity.except(:"#i:type"))
add_record_response = api.add_record_to_list(phone_contact, "API Test")
if add_record_response[:add_record_to_list_response][:return][:list_records_inserted] != '0'
phone_contact.update(:loaded_at => Time.now)
end
end
This code is taking an array of hashes and creating a new phone_contact for each one. It then makes an api call (add_record_response) to do something with that phone_contact. If that api call is successful, it updates the loaded_at attribute for that specific phone_contact. Then it starts the loop over.
I am allowed something like 7200 api calls per hour with this service - However, I'm only able to make about 1 api call every 4 seconds right now.
Any thoughts on how I could speed this code block up to make faster api calls?
I would suggest using a thread pool. You can define a unit of work to be done and the number of threads you want to process the work on. This way you can get around the bottleneck of waiting for the server to response on each request. Maybe try something like (disclaimer: this was adapted from http://burgestrand.se/code/ruby-thread-pool/)
require 'thread'
class Pool
def initialize(size)
#size = size
#jobs = Queue.new
#pool = Array.new(#size) do |i|
Thread.new do
Thread.current[:id] = i
catch(:exit) do
loop do
job, args = #jobs.pop
job.call(*args)
end
end
end
end
end
def schedule(*args, &block)
#jobs << [block, args]
end
def shutdown
#size.times do
schedule { throw :exit }
end
#pool.map(&:join)
end
end
p = Pool.new(4)
list_entries.do |list_entry|
p.schedule do
phone_contact = PhoneContact.create(list_entity.except(:"#i:type"))
add_record_response = api.add_record_to_list(phone_contact, "API Test")
if add_record_response[:add_record_to_list_response][:return][:list_records_inserted] != '0'
phone_contact.update(:loaded_at => Time.now)
end
puts "Job #{i} finished by thread #{Thread.current[:id]}"
end
at_exit { p.shutdown }
end
my resque worker class is:
require 'resque'
require 'resque/job_with_status'
class PatstatResqueWorker < Resque::JobWithStatus
#queue = :my_worker_q
def self.perform(query, label)
puts "query:"
puts options['query']
puts "label:"
puts options['label']
end
end
and my controller part, where I call this resque is...
class MyController < ApplicationController
def resque
job_id = PatstatResqueWorker.create(:query => #query, :label => "yes")
status = Resque::Plugins::Status::Hash.get(job_id)
end
end
and its not working :(
if i remove the parameter from resque function it says Wrong number of arguments (2 for 0) and if i add the parameter section back it says options not defined :(
Could you help?
The reason you're getting the "options not defined" error is that you haven't defined options in the method that uses it. Your self.perform method expects to receive two distinct arguments, query and label, but the code inside the method expects to have an options hash. You've got to choose one or the other.
Either do this:
def self.perform(query, label)
# use the parameters we've already defined
puts "query:"
puts query
puts "label:"
puts label
end
# call it like this
PatstatResqueWorker.create(#query, "yes")
Or else do this:
# change the method signature to match what you're doing
def self.perform(options)
puts "query:"
puts options['query']
puts "label:"
puts options['label']
end
# call it like this, with string keys
PatstatResqueWorker.create('query' => #query, 'label' => "yes")
Notice that with the hash version, I changed the call to use strings for the hash keys instead of symbols. You can use symbols if you want, but you'd have to change it in the body of the method as well (i.e. options[:query] instead of options['query']). You've just got to be consistent.
Just wondering how to test that actionmailer requests are actually sent to the delayed_job que in rspec.
I would have assumed it was quite simple, but my delayed_job queue doesn't seem to be incrementing. Code below:
Controller:
def create
#contact = Contact.new(params[:contact])
if #contact.save
contactmailer = ContactMailer
contactmailer.delay.contact_message(#contact)
redirect_to(contacts_url)
else
render :action => "new"
end
Spec:
it "queues mail when a contact is created" do
expectedcount = Delayed::Job.count + 1
Contact.stub(:new).with(mock_contact()) { mock_contact(:save => true) }
post :create, :contact => mock_contact
expectedcount.should eq(Delayed::Job.count)
end
Both before and after the call to the controller, the Delayed::Job.count returns 0. I've tried taking the conditional out of the controller, but I still can't get the delayed job count to increment.
Any suggestions appreciated - cheer
You can also test what the jobs will do by running them or turning off queuing.
Tweak config whenever you want (i.e. in a before :each block).
Delayed::Worker.delay_jobs = false
or perform your saved jobs
Delayed::Worker.new.work_off.should == [1, 0]
I have been using this method happily for a while. For one thing, using the new any_instance support in RSpec, you can test your delayed methods effects directly. However, I've found tests that use work_off to be slow.
What I usually do now is:
mock_delay = double('mock_delay').as_null_object
MyClass.any_instance.stub(:delay).and_return(mock_delay)
mock_delay.should_receive(:my_delayed_method)
Then I have a separate spec for my_delayed_method. This is much faster, and probably better unit testing practice -- particularly for controllers. Though if you're doing request specs or other integration-level specs, then you probably still want to use work_off.
I think your mock object is somehow introducing an error -- it's hard to tell exactly how without seeing the definition of the mock_contact method.
In any case, you might try something along these lines:
it "queues mail when a contact is created" do
Contact.stub(:new) { mock_model(Contact,:save => true) }
Delayed::Job.count.should == 0
post :create, {}
Delayed::Job.count.should == 1
end
or the sexier version (caveat: I always end up doing it the non-sexy way):
it "queues mail when a contact is created" do
Contact.stub(:new) { mock_model(Contact,:save => true) }
expect {
post :create, {}
}.to change(Delayed::Job.count).by(1)
end
You can also follow the convention (from Railscast 275) of
ActionMailer::Base.deliveries.last.to.should == user.email
but instead do this:
Delayed::Job.last.handler.should have_content(user.email)
This thread is a bit old, but here is my go at it:
Create a function expect_jobs
def expect_jobs n, time = nil
expect(Delayed::Job.count).to eq(n)
Timecop.travel(time) unless time.nil?
successes, failures = Delayed::Worker.new.work_off
expect(successes).to eq(n)
expect(failures).to eq(0)
expect(Delayed::Job.count).to eq(0)
Timecop.travel(Time.now) unless time.nil?
end
Then simply call it before checking if the callback has done its job. eg:
it "sends a chapter to the admin user" do
post :chapter_to_user, { chapter: #book.chapters.first}
expect_jobs(1)
SubscribeMailer.should have(1).delivery
SubscribeMailer.deliveries.should have(1).attachment
end
This seems to work on my side, and allows me to run both my delayed jobs and my methods.
#zetetic I think we have to pass block in change method here.
It shoulb be like this:
it "queues mail when a contact is created" do
Contact.stub(:new) { mock_model(Contact,:save => true) }
expect {
post :create, {}
}.to change { Delayed::Job.count }.by(1)
end