PaperTrail not regarding any customizations - ruby-on-rails

On a basic setup with latest Rails (7.0) and PaperTrail gem (13.0) any customizations to PaperTrail's version class are not regarded.
Following the official Readme it is supposed to work like this:
# app/models/paper_trail/version.rb
module PaperTrail
class Version < ActiveRecord::Base
include PaperTrail::VersionConcern
before_validation :print_something
def print_something
puts '!!!'
end
end
end
However, although version records are saved to the database nothing gets printed. If I open rails console and paste the above code, then saving a user (or any other record with has_paper_trail) it does work. Why is this code not loaded properly in the first place? What am I missing?

Related

How can I get the ActiveModel::API reference to work?

I have an existing simple rails app that I'm trying to modify.
I'm working on getting an ActiveModel model to work in this app (existing models all use dynamoid). I built the class and was able to get it to successfully populate a show view.
However, everything blows up when I try to switch to an edit view.
I believe the fix for this is to add include ActiveModel::API inside the class.
At least that is what the docs seem to suggest.
However, when I do that, I get
NameError (uninitialized constant ActiveModel::API)
I found the right gem and installed it gem install activemodel and then tried again, but even with the right gem, the same error occurs. I've tried restarting the rails app - no luck. I've tried adding in a require 'active_model' at the top of the file (as suggested in some probably unrelated github issue) - no luck. I dug through the gemfile.lock and found that it already required an older version of activemodel, so I uninstalled the newer version and installed the older version, restarted rails again, but no luck. Searching for the error message results in red herrings - usually dealing with class-referencing issues within an app rather than with referencing a gem.
If it matters here are my versions:
ruby: 2.7.4
activemodel: 6.1.3.1
rails: 6.1.3.1
Here is what the beginning of my class looks like:
# app/models/complex_model.rb
require 'active_model'
class ComplexModel
include ActiveModel::API
My question: How can I get past this uninitialized constant issue?
You're using the docs for the wrong version of Rails.
ActiveModel::API was introduced in Rails 7. And frankly there is not a whole lot going on if you strip the comments out:
module ActiveModel
module API
extend ActiveSupport::Concern
include ActiveModel::AttributeAssignment
include ActiveModel::Validations
include ActiveModel::Conversion
included do
extend ActiveModel::Naming
extend ActiveModel::Translation
end
def initialize(attributes = {})
assign_attributes(attributes) if attributes
super()
end
# Indicates if the model is persisted. Default is +false+.
#
# class Person
# include ActiveModel::API
# attr_accessor :id, :name
# end
#
# person = Person.new(id: 1, name: 'bob')
# person.persisted? # => false
def persisted?
false
end
end
end
What they did is basically shuffle the parts out of the ActiveModel::Model module into yet another layer. ActiveModel::Model consists of parts that where abstracted out of ActiveRecord in Rails 5 and made usable for non-database backed models and as a foundation blocks for building things like Object Relational Managers and even API client data wrappers.
The idea is that ActiveModel::API will contain just the bare necissities needed to interact with stuff like the polymorphic routing and the I18n module while ActiveModel::Model will at some point include ActiveModel::Attributes which is incompatible with ORMs that provide their own attributes implementation such as ActiveRecord.
Whatever you're doing in Rails 6 you can probally solve it simply including ActiveModel::Model and ActiveModel::Attributes.

How to properly extend ApplicationRecord in Rails 6 with Zeitwerk

Consider a Rails 6 application that has app/models/application_record.rb. This Rails 6 application is using Zeitwerk loader.
class ApplicationRecord
end
If I want to add functionality to ApplicationRecord via a module:
# app/models/concerns/fancy_methods.rb
module FancyMethods
def fancy_pants
puts "I'm wearing fancy pants"
end
end
and do the following:
class ApplicationRecord
include FancyMethods
end
I will get a deprecation warning or error:
DEPRECATION WARNING: Initialization autoloaded the constant FancyMethods.
Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.
Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload FancyMethods, for example,
the expected changes wont be reflected in that stale Module object.
This autoloaded constant has been unloaded.
Please, check the "Autoloading and Reloading Constants" guide for solutions.
(called from <top (required)> at /Users/peter/work/recognize/config/environment.rb:5)
I've read lots of articles including the Rails autoload docs, but nothing really addresses this minimal but common case of extending ApplicationRecord. Yes, I could wrap ApplicationRecord in a .to_prepare block like:
Rails.configuration.to_prepare do
class ApplicationRecord < ActiveRecord::Base
include FancyMethods
end
end
But this seems like a code smell and could cause other unexpected problems now or down the line.
Figured it out! The issue was there was an explicit require in an initializer that loaded ApplicationRecord.
# config/initializers/setup_other_fancy_thing.rb
require 'application_record'
module OtherFancyThing
def also_fancy
puts 'also fancy'
end
end
ApplicationRecord.send(:include, OtherFancyThing)
The way I debugged this was that I:
Created a new Rails app of the same version and could not reproduce the error
I copied the default application.rb and development.rb and still got the error
Moved the entire config/initializers directory to a temp directory and it made the warning go away! So, I knew it had to be one of the initializers. From there it was just a matter of dividing and conquering until I found the offending initializer.
Great! So, basically, you do not need to define the module and include it that way. Idiomatically, you define the module in its own file, normal:
# app/models/other_fancy_thing.rb
module OtherFancyThing
def also_fancy
puts 'also fancy'
end
end
and then
# app/models/application_record.rb
class ApplicationRecord
include FancyMethods
end
Whenever ApplicationRecord is loaded, for example as a side-effect or loading some regular model, it will load FancyMethods just fine.

Rails concern getting stuck in -cache-?

I have a model BusinessLog:
class BusinessLog < ActiveRecord::Base
include BusinessLogging
default_scope { where(business_id: Business.current.id) }
end
it performs operations in a default scope and includes a concern BusinessLog:
class BusinessLog < ActiveRecord::Base
module BusinessLogging
extend ActiveSupport::Concern
module ClassMethods
def log!(options)
create! business_id: options[:business_id], user_id: options[:user_id], action: options[:action]
end
end
end
end
this log! method was being called by a sidekiq worker. Eventually I discovered I couldn't log for several business because of BusinessLog default scope. So I removed the BusinessLog default scope, but it was somehow stuck.
Whenever I tried to perform operations through the module (eg: BusinessLog.log!(options)), it was still working with a default scope (even thought I already removed it). But when I performed operations in the model itself (eg: BusinessLog.create!(options)), it was working as expected, without the -already gone- default scope.
I tried a lot of things to make it work, but only one thing worked: Renamed the module and everything worked perfectly again.
It's worth saying that this just happened on my production environment.
Has anyone been through this? Should I post it as an issue on github? How could I give more information about this -bug-?
I'm using Rails 4.0.1 and ruby 2.0.0

Overriding model in gem, adding callback and methods

I've installed the ActiveRecord Reputation System on my app. How would I go about overriding or adding a callback/method to Evaluation model?
In general how do you add to any model for a gem you installed?
Simply reopen the class:
module ReputationSystem
class Evaluation < ActiveRecord::Base
def my_method_here
puts "Yey!"
end
end
end
You can put this file in config/initializers/my_monkey_patch.rb or in lib/my_monkey_patch.rb, but the later must be loaded into your code.

How to extend a mountable engine's model inside another mountable engine with development environment reloading

Using Rails 3.2.2 and Ruby 1.9.2.
I have a rails mountable engine EngineA that declares a User class inheriting form ActiveRecord::Base. I have another engine EngineB that wants to inject functionality into EngineA::User. Right now what I have done is shown below:
Method 1:
#EngineA app/models/engine_a/user.rb
module EngineA
class User < ActiveRecord::Base
has_attached_file :avatar
has_many :somethings
end
end
#EngineB lib/engine_b/user.rb
module EngineB
module User
def self.extended obj
obj.class_eval do
has_many :something_elses
end
end
end
end
EngineA::User.extend EngineB::User
This gives me an uninitialized constant EngineA::User error. Even when I require that specific file I run into the problem of EngineA needing paperclip so that has_attached_file is understood. That road ended when I realized I would have to know and require the dependencies for EngineA inside EngineB.
Method 2:
I used the same code as before except I removed the last line EngineA::User.extend EngineB::User from the EngineB user.rb file. I then moved that call to an initializer inside EngineB.
#EngineB config/initializers/my_mixin.rb
EngineA::User.extend EngineB::User
This worked perfectly!!! Except in development mode when I would change code and the models would refresh. The only thing that was refreshed was the EngineA::User and not the mixin that I had put as an initializer. So once I changed code, I lost all of my extended functionality.
I'm not even positive this is the most 'efficient' way to do this... any help would be greatly appreciated. Thanks in advance.
According to the configuration documentation, you can use an ActionDispatch callback to load items. These callbacks will run when at every request if cache_classes is set to false, like in development mode.
Inside of your EngineB.rb file, you might try something like this:
if Rails.env.development?
ActionDispatch::Callbacks.to_prepare do
load "#{File.expand_path(File.dirname(__FILE__))}/../config/initializers/my_mixin.rb"
end
end

Resources