Rails run object method on itself periodically - ruby-on-rails

I need to run a method once daily in a Rails application.
I'm thinking about using the whenever gem, but I realize I have a bit of a problem.
I need to run the method on an instance of an object. So I can't just have something like:
every 1.day do
runner 'Object.method'
end
I'm thinking about doing something like
every 1.day do
runner 'Object.where(status: "the_status_i_want").each { |object| object.method }'
end
But I have no clue how to test this behavior from within 'whenever.'
Any ideas?

Ryan Bates has a nice episode on integrating whenever gem into Rails
http://railscasts.com/episodes/164-cron-in-ruby-revised
(There is also a free version on that site, but it's kinda old, http://railscasts.com/episodes/164-cron-in-ruby)

Related

Rails method global params kind_in error when upgraded

I've recently done an rails and ruby upgrade, we don't have strong params in the app (I know it's legacy).
So the way it's done in the app we have the following
def all_params_permitted(this_params = nil)
this_params = params if this_params == nil
this_params.permit!
this_params.each do |i, v|
if v.kind_in?([Hash, ActionController::Parameters])
all_params_permitted(v)
end
end
end
Which loops through all params and just accepts everything, all_params_permitted is called throughout the app I would love to add strong params but that's a no-go for now.
The issue in the above method is kind_in? the upgrade I did for this app was rails 5.0.3 to rails 6.1+ and went from ruby 2.2.6 to ruby 3.0.1 so I'm not sure why kind_in? has stopped working. This is an old app (built-in rails 2) so not sure if this has been deprecated.
Any help here would be great.
Edit
I have tried kind_of? but no dice.
the upgrade I did for this app was rails 5.0.3 to rails 6.1+ and went from ruby 2.2.6 to ruby 3.0.1
This is asking for trouble. It is strongly advised to try upgrading one minor version at a time (e.g. rails 5.0 --> 5.1 --> 5.2 --> 6.0 --> 6.1), otherwise you're very likely to break things with little information on why it's stopped working/how to fix it.
Likewise for ruby versions... At an absolute minimum I'd postpone the final upgrade to ruby v3 until your application works fine under ruby 2.7.
I'm not sure why kind_in? has stopped working
Nor am I, because that's a custom method. You haven't show us how it's defined, and nor have you shown us the error message, so it's impossible for me to say with confidence what's gone wrong.
My guess is that it's implemented something like this:
class Object
def kind_in?(classes)
classes.any? { |c| self.kind_of?(c) }
end
end
i.e. it's a little wrapper around the built-in kind_of? method.
And with that said, I still have no idea why this would have "stopped working" due to a ruby and/or rails upgrade.
Not sure about kind_in?, also didn't find any reference to that method, also as you have not posted the error so not sure about your issue. is_a?, kind_of?, instance_of? are few methods that check the object class but they check only a single class. Looking at your code one option for your condition could be:
if [Hash, ActionController::Parameters].include?(v.class)
which will check if it belongs to one of these classes.

Rails 4 - Impressionist Inside of a Resque Background Job

I am using Rails 4 w/ the impressionist and resque gem.
I am using impressionist to log unique session hits on my article show page. Due to performance issues and no need to display hits to users (it is for admins only), I would like to move logging impressions off into the background.
Normally I would log an impression using impressionist(#article, unique: [:session_hash]) but to move it off into the bg via resque I am now doing something like this...
articles_controller:
def show
.
.
.
Resque.enqueue(ImpressionLogger, #article.id)
end
app/workers/impression_logger.rb:
class ImpressionLogger
#queue = :impression_queue
def self.perform(article_id)
article = Article.find(article_id)
impressionist(article, unique: [:session_hash])
end
end
When I set it up like this, when resque tries to process the job, it is returning undefined method "impressionist" for ImpressionLogger:Class. What do you guys think the best way to go about this is? I am not sure how to include impressionist methods inside of my resque worker.
The issue
Your problem stems from the fact that it looks like Impressionist works on the controller level due to including a module with the impressionist method in an engine initializer on any instances of ActionController:
https://github.com/charlotte-ruby/impressionist/blob/master/lib/impressionist/engine.rb#L11
You're trying to call the impressionist method from a regular class being invoked in a Resque job, so it's not going to have that method defined.
Solution
It's kind of gross, but if you really want to use impressionist, we can delve into this... Looking at the actual implementation of the impressionist method found here, we see the following:
def impressionist(obj,message=nil,opts={})
if should_count_impression?(opts)
if obj.respond_to?("impressionable?")
if unique_instance?(obj, opts[:unique])
obj.impressions.create(associative_create_statement({:message => message}))
end
else
# we could create an impression anyway. for classes, too. why not?
raise "#{obj.class.to_s} is not impressionable!"
end
end
end
Assuming that you'd be calling something like this manually (as you want to from a resque job) the key are these three lines:
if unique_instance?(obj, opts[:unique])
obj.impressions.create(associative_create_statement({:message => message}))
end
The if wrapper only seems to be important if you want to implement this functionality. Which it looks like you do. The call to associative_create_statement seems to be pulling parameters based off of the controller name as well as parameters passed from Rack such as the useragent string and ip address (here). So, you'll have to resolve these values prior to invoking the Resque job.
What I would suggest at this point is implementing a Resque class that takes in two parameters, an article_id and the impression parameters that you want. The resque class would then just directly create the impression on the impressionable object. Your Resque class would become:
class ImpressionLogger
#queue = :impression_queue
def self.perform(article_id, impression_params = {})
article = Article.find(article_id)
article.impressions.create(impression_params)
end
end
And your controller method would look something like this:
def show
.
.
.
Resque.enqueue(ImpressionLogger, #article.id, associative_create_statement({message: nil})) if unique_instance?(#article, [:session_hash])
end
Disclaimer
There's a fairly big disclaimer that comes with doing it this way though... the method associative_create_statement is marked protected and unique_instance? is marked private... so neither of these is part of the impressionist gem's public API, so this code might break between versions of the gem.
Is impressionist installed properly with bundler? If so Rails should be loading it into your environment. I would check whether you can access impressionist functionality elsewhere in your Rails code (i.e. without going through Resque) as the first step to debugging this.
How are you starting your resque workers? If you need your Rails environment loaded, try rake environment resque:work.
https://github.com/resque/resque/wiki/FAQ#how-do-i-ensure-my-rails-classesenvironment-is-loaded

Delayed_job won't make any changes to database

I am having many problems running DJ. Primarily, I cannot get delayed_job running any methods that change the database. I am testing locally with a sqlite3 database, DJ 3.0.0, and I even added the delayed_job_active_record gem.
I have, for example, tried to run the following method in the background:
#user = User.find(1)
#user.delay.recorder_method
Where this method is:
def recorder_method
self.relevant_field +=1
update
end
This creates a delayed job, and the handler has the appropriate info for the user and the appropriate method name. The script runs the job, thinks it has succeeded and thus deletes the record from the delayed_job table. BUT the user is unchanged (the database is unchanged).
What on earth is going wrong? Note that, when I run the same code ("#user.delay.recorder_method") from rails console, it works.. and the difference is the handler created by the rails console call is:
--- !ruby/object:Delayed::PerformableMethod
object: !ruby/ActiveRecord:User
attributes:
... (attribute info and rest of file)
Whereas the one created in by the call in a controller action is:
--- !ruby/struct:Delayed::PerformableMethod
object: !ruby/ActiveRecord:User
attributes:
Not the difference in line 1 of both things (ruby/object vs ruby/struct.. the former works and the latter doesn't). Maybe this is something that might signal what is going wrong. Any ideas, anybody?
Your Rails app is running an old version of the DJ gem and your console is running a new one. About six months ago, Delayed::PerformableMethod was refactored from a Struct into a regular class: https://github.com/collectiveidea/delayed_job/commit/7b8a79a72c0ee5d8bac4bc0b183d1cce9cedff85 (So your Rails app is running a gem at least six months old and your console is running one newer than that).
This is an easy fix. First update the appropriate line in your Rails Gemfile. You'll see a line like this:
gem 'delayed_job_active_record'
If it has a version specification, make sure it's updated to the newest. Then from the command line (in the Rails root), run:
bundle update delayed_job_active_record
For anyone interested, removing the following lines from boot.rb solved this issue for me:
require 'yaml'
YAML::ENGINE.yamler = 'syck'

How to get generators call other generators in rails 3

I am experimenting with gem development, right now specifically generators. So far I have successfully created two generators that do their job just perfectly. These two generators are in the same directory.
However, right now I have to call each of them separately.
What I'd like to do is just call one generator and have that generator call all the other ones. Just would type
rails g generator_name
and this would call x other generators.
Does anyone know how would I got about doing this?
Help is much appreciated, thanks!
In your generator, you can just call
generate "some:generator" # can be anything listed by 'rails g'
for example:
module MyGem
class InstallGenerator < Rails::Generators::Base
def run_other_generators
generate "jquery:install" # or whatever you want here
end
end
end
By the way, if you are working on Rails 3 gems, this question can also help out:
Rails 3 generators in gem
Another possibility is to use something like
invoke 'active_record:model', 'foo bar:string baz:float'
which is not as clean as generate, but has one advantage: When your generator gets called via rails destroy, this call -- like may other of Thors actions -- will try to revoke the action of the generator you invoke.
There's a catch however: Probably due to Thors dependency management, this only works once per generator you want to call, meaning that a second invoke of the same generator will do nothing. This can be circumvented by using a statement like
Rails::Generators.invoke 'active_record:model', '...', behavior: behavior
instead. In this case you have to explicitly pass through the behavior of your generator (which is a method returning values like :invoke, :revoke and possibly others, depending on which command -- rails generate, rails destroy, rails update, etc. -- called your generator) to achieve the same result as above. If you don't do this, the generator you call with Rails::Generators.invoke will also be executed when running your generator with rails destroy.
Alternatively you could stick to invoke and try to tamper with Thors invocation system. See also here for example.
Generators are based off of Thor, so you can use the apply method.
This is what the Rails Templater gem does. (Here's a walk through the Rails Templater gem.)
Take a look at the scaffold generator that comes with rails.
/Users/XYZ/sources/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb
def manifest
record do |m|
#....rest of the source is removed for brevity....
m.dependency 'model', [name] + #args, :collision => :skip
end
end
Here the scaffold generator is using the model generator. So take a look at the dependency method. You can find the API docs for it over here.

How do I use a custom log for my rake tasks in Ruby on Rails?

I have a rake task that calls functions like this:
namespace :blah do
task :hello_world => :environment do
logger.info("Hello World")
helloworld2
end
end
def helloworld2
logger.info("Hello Again, World")
end
I want the log output to a custom log, and I really don't want to have to pass a log reference every time I make a function call. I found this somewhere (can't find it again):
def logger
##logger ||= Logger.new("#{RAILS_HOME}/log/blah.log")
end
But this does not work for me and I am not sure what it even does because I grabbed the code a long time ago and haven't used it until now. I can't search for ## on google (tried +"##" rails) to see what it does. Any help on this issue would be great. I am hoping for a quick solution and not having to install a gem or plugin (unless there is a really really good reason to.
Thanks!
rake disables logging in production mode. make sure you're running in development mode if you want it to log
What do you mean by "does not work for me"? I just tried this same code and it worked - created a new log file and put some text in it.
##logger is a class variable, it's a language issue, not Rails' one. I believe there's no need in further explanations :)
You've probably mistaken typing "function helloworld2" :)
Advanced Rails Recipes Recipe 84 from #topfunky shows how to define a custom logger. He has some code in the environment config file (production would look like this): RAILS_ROOT/config/environments/production.rb:
config.logger = RAILS_DEFAULT_LOGGER = Logger.new(config.log_path)
I'd test that out instead of redefining the class variable as you have. He might have something on http://nubyonrails.com to check as well.

Resources