Delayed_job : defining time - delayed-job

So I'm trying to figure out how to do this cleanly. Trying to keep the example simple lets say I have an object, MyMailMeeting in which I define two times that I want to send two different emails to people in a meeting.
In my MyMailMeeting model, I want to define both methods (rather than having their own delayed job class), but I need to have those methods see the times defined within the object in order to know when to send.
def send_first
... do stuff
end
handle_asynchronously :send_first, :run_at => Proc.new { send_first_time }
Problem is that according to the documentation, send_first_time needs to be a class method, and want it to be an instance method so that I can see the times defined by the user.
How do I do this? Or do I just need to create two separate Delayed_job classes and do something like this:
Delayed::Job.enqueue(SendFirstJob.new, :run_at => send_first_time)

I believe that handle_asynchronously passes the object into Procs for its attributs so:
handle_asynchronously :send_first, :run_at => Proc.new { |obj| obj.send_first_time }
You can always roll your own async wrapper
def send_first_async(*args)
delay(:run_at => send_first_time).send_first(*args)
end

I ended up using the second method. Though it isn't as time sensitive as I would like.
If anyone has an answer on how to get send_first_time to be a variable that is based on user input, I'll gladly accept it as the right answer. Thanks.

Related

Can you cross model data in the seuros state machine?

Synopsis
In Ruby on Rails, does the state machine gem support the use of a model instance that doesn't directly relate to the host model? If they do, how do I do it?
The conclusion I'm leaning toward is that authorization should be left to other parts of the framework, and the state machine should just be an interface defining the transition of states. That being said, I see some support for transition conditions and I was wondering if the data inside those conditions could be something NOT set on the host model, but instead passed in like a parameter.
Background
Say we have a Task that has the states in_progress and completed, and in order to transition from them respectively, the current_user (assigned in the session, access in the controller) needs to pass a check.
I understand through the documentation that in order to add a check to the transition I have to program it like this:
transition :in_progress => :completed, :if => :user_is_owner?
and define the function like:
def user_is_owner()
true
end
but let's try to implement the restriction so that the task can only be edited if the user_id is the same as the id of the user that requested the task USING dynamic data.
def user_is_owner?(user)
user.id == self.requester_id
end
Notice I don't have that user object, how would one pass the user object they need in?
Ruby Version: 1.9.3
Rails Version: 3.2.9
Thanks!
The thought process behind this post was that I wanted to use the framework the way it was meant to be used, MVC. Information specific to the connection doesn't belong on a model that represents something completely independent of the connection, it's just logical.
The solution I chose for my problem was what #SergioTulentsev mentioned, A transient attribute.
My Ruby on Rails solution included setting up a transient attribute on my model, by adding an attr_accessor
attr_accessor :session_user
and a setter
# #doc Setter function for transient variable #session_user
def session_user
#session_user
end
and a function that uses the setter on my Task model
def user_is_owner?
requester == session_user
end
then I utilized that function inside of my state_machine's transition
transition :completed => :archived, :if => :user_is_owner?
The problems I see with this are that anytime you want to use the User to make authorization checks, you can't just pass it in as a parameter; it has to be on the object.
Thanks, I learned a lot. Hopefully this will be somewhat useful over the years...
The original response is a valid approach, but I wound up going with this one. I think it's a much cleaner solution. Override the state machine events and extract the authorization.
state_machine :status, :initial => :new do
event :begin_work do
transition :new => :in_progress
end
end
def begin_work(user)
if can_begin_work?(user)
super # This calls the state transition, but only if we want.
end
end
Sources:
https://github.com/pluginaweek/state_machine/issues/193
https://www.rubydoc.info/github/pluginaweek/state_machine/StateMachine%2FMachine:before_transition
Passing variables to Rails StateMachine gem transitions

How do I create a transaction out of multiple Rails save methods?

I'm using Rails 5. I have a model that looks like this
class CryptoIndexCurrency < ApplicationRecord
belongs_to :crypto_currency
end
I have a service method where I want to populate this table with records, which I do like so
CryptoIndexCurrency.delete_all
currencies.each do |currency|
cindex_currency = CryptoIndexCurrency.new({:crypto_currency => currency})
cindex_currency.save
end
The problem is the above is not very transactional, in as far as if something happens after the first statement, the "delete_all" will have executed but nothing else will have. What is the proper way to create a transaction here and equally as important, where do I place that code? Would like to know the Rails convention here.
I think you can just do:
CryptoIndexCurrency.transaction do
CryptoIndexCurrency.delete_all
CryptoIndexCurrency.create(currencies.map{ |c| {crypto_currency: c} })
end
If you are using Activerecord you can use the builtin transaction mechanism. Otherwise, one way would be to make sure you validate all your data and only save when everything is valid. Take a look at validates_associate and the like.
That said, if your process is inherently non validatable/nondeterministic (eg. you call external APIs to validate a payment) then the best is to ensure you have some cleaning methods that take care of your failure
If you have deterministic failures:
def new_currencies_valid?(currencies)
currencies.each do
return false if not currency.valid?(:create)
end
true
end
if new_currencies_valid?(new_currencies)
Currency.delete_all # See note
new_currencies.each(&:save)
end
A sidenote : unless you really understand what you are doing, I suggest calling destroy_all which runs callbacks on deletion (such as deleting dependent: :destroy) associations

Testing an association model helper method rails rspec

I have two models, User and Account.
# account.rb
belongs_to :user
# user.rb
has_one :account
Account has an attribute name. And in my views, I was calling current_user.account.name multiple times, and I heard that's not the great of a way to do it. So I was incredibly swift, and I created the following method in my user.rb
def account_name
self.account.name
end
So now in my view, I can simply call current_user.account_name, and if the association changes, I only update it in one place. BUT my question is, do I test this method? If I do, how do I test it without any mystery guests?
I agree there is nothing wrong with current_user.account.name - while Sandi Metz would tell us "User knows too much about Account" this is kind of the thing you can't really avoid w/ Active Record.
If you found you were doing a lot of these methods all over the User model you could use the rails delegate method:
delegate :name, :to => :account, :prefix => true
using the :prefix => true option will prefix the method in the User model so it is account_name. In this case I would assume you could write a very simple unit test on the method that it returns something just incase the attribute in account would ever change your test would fail so you would know you need to update the delegate method.
There's nothing wrong with current_user.account.name
There's no difference between calling it as current_user.account.name, or making current_user.account_name call it for you
You're probably not calling current_user in the model, like you say
You should have a spec for it if you use it
Personally I see no good reason for any of this. Just use current_user.account.name.
If you are worrying about efficiency, have current_user return a user that joins account.
This is going to be a bit off-topic. So, apologies in advance if it's not interesting or helpful.
TL;DR: Don't put knowledge of your models in your views. Keep your controllers skinny. Here's how I've been doing it.
In my current project, I've been working to make sure my views have absolutely no knowledge of anything about the rest of the system (to reduce coupling). This way, if you decide to change how you implement something (say, current_user.account.name versus current_user.account_name), then you don't have to go into your views and make changes.
Every controller action provides a #results hash that contains everything the view needs to render correctly. The structure of the #results hash is essentially a contract between the view and the controller.
So, in my controller, #results might look something like {current_user: {account: {name: 'foo'}}}. And in my view, I'd do something like #results[:current_user][:account][:name]. I like using a HashWithIndifferentAccess so I could also do #results['current_user']['account']['name'] and not have things blow up or misbehave.
Also, I've been moving as much logic as I can out of controllers into service objects (I call them 'managers'). I find my managers (which are POROs) a lot easier to test than controllers. So, I might have:
# app/controllers/some_controller.rb
class SomeController
def create
#results = SomeManager.create(params)
if #results[:success]
# happy routing
else
# sad routing
end
end
end
Now, my controllers are super skinny and contain no logic other than routing. They don't know anything about my models. (In fact, almost all of my controller actions look exactly the same with essentially the same six lines of code.) Again, I like this because it creates separation.
Naturally, I need the manager:
#app/managers/some_manager.rb
class SomeManager
class << self
def create(params)
# do stuff that ends up creating the #results hash
# if things went well, the return will include success: true
# if things did not go well, the return will not include a :success key
end
end
end
So, in truth, the structure of #results is a contract between the view and the manager, not between the view and the controller.

ActiveRecord Callbacks List

I've been going through the rails source for a while now, and I don't think there's a better way of getting the list of all callbacks other than: ActiveRecord::Callbacks::CALLBACKS – which is a constant list.
Meaning if you're using a gem like devise_invitable that adds a new callback called :invitation_accepted with the score :after and :before then ActiveRecord::Callbacks::CALLBACKS will not work.
Do you know of an easy fix, other than opening up rails modules and making sure there's an internal list of call-backs per model class?
You can call Model._save_callbacks to get a list of all callbacks on save.
You can then filter it down to what kind you need e.g. :before or :after like this:
Model._save_callbacks.select {|cb| cb.kind == :before}
Works the same for Model._destroy_callbacks etc.
The docs say "There are nineteen callbacks in total"... but they don't seem to say what all of those 19 actually are!
For those who Googled "list of all ActiveRecord callbacks" like I did, here's the list (found by using ActiveRecord::Callbacks::CALLBACKS as described in the question):
:after_initialize
:after_find
:after_touch
:before_validation
:after_validation
:before_save
:around_save
:after_save
:before_create
:around_create
:after_create
:before_update
:around_update
:after_update
:before_destroy
:around_destroy
:after_destroy
:after_commit
:after_rollback
Note that if you simply want to trigger callbacks, you can use the #run_callbacks(kind) method:
o = Order.find 123 # Created with SQL
o.run_callbacks(:create)
o.run_callbacks(:save)
o.run_callbacks(:commit)
If you're working in a Rails version prior to the ._save_callbacks method, you can use the following:
# list of callback_chain methods that return a CallbackChain
Model.methods.select { |m| m.to_s.include? "callback" }.sort
# get all methods in specific call back chain, like after_save
Model.after_save_callback_chain.collect(&:method)
I am going to propose most universal solution.
It works even when gems are declaring custom callbacks e.g. paranoia and after_real_destroy
To list all callbacks
Model.methods.select { |m| m.to_s.include? "callback" }.sort
Then you can get given callbacks if you type method name e.g.
Model._update_callbacks
Model._real_destroy_callbacks
Model._destroy_callbacks
If you list all callbacks, then you can find callback you need by checking #filter instance variable e.g.
require 'pp'
Activity._destroy_callbacks.each_with_index { |clbk,index| puts "#{index}-------\n#{clbk.pretty_inspect}" } ; nil
# [...]
#<ActiveSupport::Callbacks::Callback:0x00007ff14ee7a968
#chain_config=
{:scope=>[:kind, :name],
:terminator=>
#<Proc:0x00007ff13fb825f8#/Users/mypc/.rbenv/versions/2.3.7/lib/ruby/gems/2.3.0/gems/activemodel-4.1.16/lib/active_model/callbacks.rb:103 (lambda)>,
:skip_after_callbacks_if_terminated=>true},
#filter=
#<Proc:0x00007ff14ee7ac10#/Users/mypc/.rbenv/versions/2.3.7/lib/ruby/gems/2.3.0/gems/activerecord-4.1.16/lib/active_record/associations/builder/association.rb:135 (lambda)>,
#if=[],
#key=70337193825800,
#kind=:before,
#name=:destroy,
#unless=[]>
4-------
#<ActiveSupport::Callbacks::Callback:0x00007ff14ee3a228
#chain_config=
{:scope=>[:kind, :name],
:terminator=>
#<Proc:0x00007ff13fb825f8#/Users/mypc/.rbenv/versions/2.3.7/lib/ruby/gems/2.3.0/gems/activemodel-4.1.16/lib/active_model/callbacks.rb:103 (lambda)>,
:skip_after_callbacks_if_terminated=>true},
#filter=:audit_destroy,
#if=[],
#key=:audit_destroy,
#kind=:before,
#name=:destroy,
#unless=[]>
5-------
For after_commit callbacks, call Model._commit_callbacks.
Be mindful, however, that there's a known bug in Rails (still present in Rails 5.2.2) that after_commit callbacks are not run in the order they are declared in the model, even tough they appear in the correct order in that _commit_callbacks call.
More info: Execution order of multiple after_commit callbacks (Rails) and https://github.com/rails/rails/issues/20911

Rails: Why does the model method not work?

its the first time I post here. I have a problem that i can somehow not solve. Just for the record, I know what instance and class methods are (even if I may not understand them completely ;-)
Here is my model code:
class Car < ActiveRecord::Base
has_many :drives
has_many :users, :through => :drives
def self.user_ids()
ids = []
self.users.each do |user|
ids += user.id
end
ids
end
def self.common_times()
start_times = []
stop_times = []
self.drives.each do |drive|
drive.start_date_time += start_times
drive.stop_date_time += stop_times
end
times = { :start => start_times.sort.last, :stop => stop_times.sort.first}
end
what I want is an array of all users using the car (which I use to check if a given user is already connected to the car for permissions etc.. Is there a better way to check if two datasets are already connected without doing SQL queries all the time?) and which start and stop times they prefer. I need than a hash with the latest starting time and the earliest stop time.
Somehow the user_ids method works (even if I think it should be an instance method) and the common_times is always missing. if I define them both as an instance method I have problems with fixnum and array stuff (like "+").
user_id:
"TypeError: can't convert Fixnum into Array"
common_times:
"NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.+"
I guess the best way is to make them instance methods. But then I need to refer differently to the other models as users and drives.
Why does user_ids work as an instance method even if declared as a class method?
How do I call already loaded models [c=Car.find(:all, :include => :drives)] inside an instance method?
Funny thing was also, that as long as they were class methods I could delete them and restart mongrel and they would still work (user_ids) and not work (common_times).
I am pretty confused right now and hop you can help me. And sorry for my bad english (I am german :-)
Because of your users association, Rails already pre-builds the user_ids and users instance methods. Using #car.users should be your best bet.
As for instance and class methods: instance methods are for specific objects, whereas class methods are just put under the class name for your convenience. #car.id is an instance method, since it returns the ID of a single car. Cars.find is a class method, since it is independent of any single object and instead is grouped under the Cars class for organizational purposes. (It could just as easily be its own global find_cars method and work just as well, though it would be horrible design.)
So both of your methods should be instance methods, and the first one Rails creates for you because it loves you so much.
As for your individual errors, adding objects to an array is done with the << operator, not the plus sign. Ruby thinks you are trying to add two arrays, so is confused why you are trying to use a Fixnum in the same way you would typically use an array. Try making those fixes and see if you still get errors.
Got it to work (thnx #Matchu). Here is the final code for the method (no self-made user_ids anymore ;-)
def common_times()
start_times = []
stop_times = []
drives.each do |drive|
start_times << drive.start_date_time
stop_times << drive.stop_date_time
end
times = { :start => start_times.sort.last, :stop => stop_times.sort.first}
end
the biggest error was the switched start_times << drive.start_date_time
Really silly error..
thanks again!

Resources