Separated logs in Rails 6 - ruby-on-rails

Long time ago it was possible to set different log files for each class or class hierarchy in Rails, just doing for instance
class ApplicationJob < ActiveJob::Base
self.logger=Logger.new('log/ActiveJob.log')
and the same in each descendent class
Now, running with rails 6 and Spring, I find that the last defined log overwrites the previous ones. Is there any other recipe?
I also notice that there is some systematic in the overwriting. For instance, perhaps all the descendent of ActiveJob get a wrong log file, but the descendents of ActiveRecord get a different logger.
Any clue about what should happen if I do self.logger=Logger.new('nameOfClass.log') for each model, controller and job?
EDIT: I have tried self.logger= in the class definition and MyClass.logger= after the definition of the class. Neither work.

Any clue about what should happen if I do
self.logger=Logger.new('nameOfClass.log') for each model, controller
and job?
Don't know about model, but i'm declaring logger = Logger.('class_name.log') in each controller and job as result can check controller_a.log or controller_b.log / job_a.log or job_b.log separately
UPDATE:
For example (controller)
# a_controller
def AController < ApplicationController
before_action :clogger
def index
#logger.info("index")
...
end
def show
#logger.info("show")
...
end
private
def clogger
#logger = Logger.new('path_to_log/a_controller.log')
end
end
# b_controller
def BController < ApplicationController
before_action :clogger
def index
#logger.info("index")
...
end
def show
#logger.info("show")
...
end
private
def clogger
#logger = Logger.new('path_to_log/b_controller.log')
end
end
And i got a and b controllers log separately. it works same with jobs.

Related

Using a helper method to set an instance variable

I have several controllers that set an instance variable, as follows:
before_action :set_max_steam_pressure
.....
def set_max_steam_pressure
# and then about a dozen lines of code concluding with
#max_steam_pressure = Valve.where(id: socket_id).first.pressure
end
This code is repeated in about a dozen controllers.
Is it possible to do this through a helper method, as part of the before_action, without having to repeat the same code in all the controllers? Benefits: less code, and if I have to change something in the future, I would only do it in one place.
You can use "controller concern", for example:
app/controllers/concerns/steam_pressure_setup.rb
module SteamPressureSetup
extend ActiveSupport::Concern
included do
before_action: set_max_stream_pressure
end
def set_max_stream_pressure
# ...
end
end
Then include it in your controllers which need it.
app/controllers/my_controller.rb
class MyController < ApplicationController
include SteamPressureSetup
# ...
end
Ref: https://api.rubyonrails.org/classes/ActiveSupport/Concern.html

How can I pass in a variable defined in a class into a Rails form?

If I have a controller
class MyController < ApplicationController
vals = [...]
def new
...
end
def create
if save
...
else
render 'new'
end
end
how can I make the "vals" variable accessible to both methods? In my "new" view I want to use the "vals" variable for a drop-down menu, but rails is giving me errors. Of course, I could just copy the variable twice, but this solution is inelegant.
As Sebastion mentions a before_ hook / callback is one way to go about it, however as you mentioned it is for a dropdown menu, I am guessing it is a non-changing list, if so I would suggest perhaps using a Constant to define the values, perhaps in the model they are specific to, or if it is to be used in many places a PORO would do nicely to keep things DRY. This will then also allow you to easily access it anywhere, for example in models for a validation check, or to set the options of the dropdown menu in the view, or in the controller if you so wish:
class ExampleModel
DROPDOWN_VALUES = [...].freeze
validates :some_attr, inclusion: { in: DROPDOWN_VALUES }
end
class SomeController < ApplicationController
def new
# can call ExampleModel::DROPDOWN_VALUES here
end
def create
# also here, anywhere actually
end
end
You could use a before_* callback, e.g a before_action, this way you sets your vals variable as an instance one and make it to be available for your both new and create methods, something like:
class SomeController < ApplicationController
before_action :set_vals, only: [:new, :create]
def new
...
# #vals is available here
end
def create
if save
...
# and here
else
render 'new'
end
end
private
def set_vals
#vals = [...]
end
end
A different way from the ones before (although probably just having the instance method is preferred as in Sebastian's solution) is, take advantage of the fact that functions and local variables are called in the same way in ruby and just write:
def vals
#vals ||= [...]
end
and you should be able to access it on the controllers (not the views). If you want it on your views as well you can call at the beginning of the controller
helper_method :vals
If you want to be able to modify vals using vals="some value"
def vals= vals_value
#vals = vals_value
end
Take into account that probably using the intance variable as in Sebastian's solution is preferred, but if you, for whatever reason, are settled on being able to call "vals" instead of "#vals" on the view (for example if you are using send or try), then this should be able to do it for you.
Define in corresponding model
Eg :
class User < ActiveRecord::Base
TYPES = %w{ type1 type2 type3 }
end
and use in ur form like
User::TYPES
=> ["type1", "type2", "type3"]
You can reuse this anywhere in the application.

Possible to DRY up the same method in two different controllers?

I'm have a Rails project which has an api part and the regular one. They have some common methods, for example, I have two controllers like so:
class PlacementController < ApplicationSafeController
def zip
file = ZipService::ZipGenerator.create_zip
send_data(file.read, type: 'application/zip')
File.delete(file.path)
end
end
and
class Api::ZipsController < ApiController
def single_zip
file = ZipService::ZipGenerator.create_zip
send_data(file.read, type: 'application/zip')
File.delete(file.path)
end
end
And both my ApiController and ApplicationSafeController inherit from ApplicationController. My question is, what is the best way to clean this up without making the root ApplicationController dirty? (by adding a new method there). Thanks!
You a module/concern to share code. Enclose shareable code in a module, then include that module in the required controllers. It's the Ruby way of doing this thing.
module Zippable
def zip
file = ZipService::ZipGenerator.create_zip
send_data(file.read, type: 'application/zip')
File.delete(file.path)
end
end
class PlacementController < ApplicationSafeController
include Zippable
#Example Usage
def show
zip
end
end
class Api::ZipsController < ApiController
include Zippable
end

Storing data in a job for use when it is performed

I need to store information at the time an Active Job is scheduled for use when it is later performed. I would like to save this information in the Active Job itself, but I'm not sure if that's possible.
Here's a simplified version of what I'm trying, which reproduces a bug I see:
class TestJob < ActiveJob::Base
queue_as :default
attr_reader :save_for_later
def initialize(info)
#save_for_later = info
end
def perform()
logger.info(#save_for_later)
end
end
class CollectionsController < ApplicationController
def schedule_test_job
TestJob.perform_later(Date.new)
end
end
When I call schedule_test_job in the Collections controller, I get an error:
undefined method `map' for nil:NilClass
and the perform action is not called.
I'm assuming I need to persist the information I'm trying to save elsewhere in my database, but I'd like to know if there is a proper way to accomplish what I'm doing here. I also don't understand where the error thrown is coming from.
Actually all the job parameters are passed to perform, so you have to write:
class TestJob < ActiveJob::Base
def perform(date)
logger.info(date)
end
end
TestJob.perform_later(Date.new)
The second problem is that Date is not AFAIK serializable by ActiveJob. But you can easily pass a string and then parse the date ;)

Extending a controller in Rails

I have a controller which calls out to another class.
class BlahController < ActionController
def index
OtherClass.get_stuff
end
end
In this class I want to be able to write controller style code.
for instance:
class OtherClass
def self.get_stuff
#foo = bar
end
end
However, I would also like #foo to exist when inside my view, but as it's a separate class those variables aren't making it back through into the controller assigns - so question is, how I can make this so?
(Ignore why I'm having to call out to a separate class, I'm trying to get this code fitting in with a legacy codebase without too much butchery)
class BlahController < ActionController
def index
OtherClass.get_stuff(self)
end
end
class OtherClass
def self.get_stuff(that)
that.instance_variable_set(:#foo, bar)
end
end
Please note that I don't agree with this method. I am just answering the question as you stated it.
I would prefer to accomplish this functionality through mixins and thereby decrease parameter coupling that is present within the code above.
Code structured like this will be difficult to read and maintain. Whenever you can, let the controller directly set all of the variables that the view needs:
class BlahController < ActionController
def index
#foo = OtherClass.get_stuff
end
end
class OtherClass
def self.get_stuff
# return the value that should be assigned to #foo
end
end

Resources