How do I set the PaperTrail logger? - paper-trail-gem

I am running into an issue when trying to reify PaperTrail versions after removing a column from the model's table.
The stacktrace begins: private method 'warn' called for nil:NilClass
and points me to the following lines inside of the reify method:
# Set all the attributes in this version on the model.
attrs.each do |k, v|
if model.has_attribute?(k)
model[k.to_sym] = v
elsif model.respond_to?("#{k}=")
model.send("#{k}=", v)
else
logger.warn "Attribute #{k} does not exist on #{item_type} (Version id: #{id})."
end
end
Because I have removed the column from the table, I am landing in the else block of the logic tree, which seems perfectly reasonable... I am fine logging this out moving on with the reification.
However, I don't understand why logger is nil in the first place. Where and how can I set the PaperTrail logger so that we simply log the behavior instead of crashing the app?

As of 2016-12-14 the answer is to upgrade to PT 6.0.2.
This was fixed in PT 6.0.2, thanks to PR https://github.com/airblade/paper_trail/pull/905 by Andy (the OP. Thanks, Andy)

I figured out two ways to accomplish this.
1) By overwriting the PaperTrail::Version class
module PaperTrail
class Version < ActiveRecord::Base
include PaperTrail::VersionConcern
def logger
Logger.new(STDOUT)
end
end
end
2) By setting ActiveRecord::Base's logger in an initializer (obviously this affects all models, so you may want to specify the log level to something higher than debug):
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.logger.level = Logger::INFO
Curious if there is another recommended way, but these appear to work for my purposes for now.

Related

uninitialised constant after upgrade of Gemfile

I've just updated my Gemfile.
At the beginning I thought problem came from Zeitwerk (from 2.4.2 to 2.5.4) but I've downgraded it and I still have an issue on my spec. I've isolated that the problem does not come from RSpec and dependencies.
Actually, RSpec does not found a class which is defined within another file and does not match the file name/class name.
Important point: Filter::MyStandardError is found.
# app/services/filter/my_standard_error.rb
module Filter
class MyStandardError < StandardError; end
class MySpecificError < MyStandardError; end
# ...
end
# app/services/filter/my_tested_service.rb
module Filter
class MyTestedService
def initialize
raise ::Filter::MySpecificError
end
end
end
RSpec.describe Filter::MyTestedService do
subject { described_class.new }
it 'raises an error'
expect{subject}.to raise_error(::Filter::MySpecificError)
end
end
And I got the error:
NameError:
uninitialized constant Filter::MySpecificError
I got the Changelog but breaking changes are not used on my configuration.
Does anybody have an idea for this one?
You do not need to add app/services to the autoload paths, that is done automatically by Rails. I'd suggest to remove that configuration to keep things simple/idiomatic.
The implementation of app/services/filter.rb should not be needed. Your application is doing something that is not right, we just need to find it.
Could you please delete app/services/filter.rb, throw Rails.autoloaders.log! in config/application.rb, trigger the error, and share the traces?
After reading one-file-one-constant-at-the-same-top-level
I found this to fix my issue
# app/services/filter.rb
class Filter
class MyStandardError < StandardError; end
class MySpecificError < MyStandardError; end
end
# app/services/filter/my_tested_service.rb
class Filter
class MyTestedService
def initialize
raise ::Filter::MySpecificError
end
end
end
I still don't know why it was working before..
You cannot define two constants at the same level in the same file. It is one constant, one file. This has not changed in Zeitwerk upgrades. You need one file for the standard error, and another file for the specific error.

Ruby: undefined method for not initialized

Note: There are numerous answers explaining that you can get this error when you subclass ActiveRecord::Base and add an #initialize without super. No answer explains what is actually happening.
I am working in someone else's code and I have an HTTParty service in a Rails app with the following class hierarchy. Note the subclass #initialize with a differing signature to the parent class.
module A
class Base
include HTTParty
...
end
end
module A
class User < Base
def initialize(user)
#user = user
end
end
end
module A
class PublicUser < User
def initialize(opts = {})
#limit = opts[:limit]
# no call to super
end
end
end
Locally there are no problems with this, but in SemaphoreCI the following results:
A::PublicUser.new(limit: 1).some_method
undefined method `some_method' for #<A::PublicUser not initialized>
I can't find any documentation about the "not initialized" message. What causes this sort of failure?
OK, I got it. I also tagged your question with ruby-on-rails, since plain good ruby would rare give such a weird behaviour.
You have experienced two different issues, more or less unrelated.
#<A::PublicUser not initialized> is a result of (sic!) calling inspect on A::PublicUser. So, ruby tries to format an error message and—voilà—the class is printed out that way.
Rails messes with you, as well as with constant lookup. A::Base name conflicts with ActiveRecord::Base, and guess what is resolved when class User < Base is met. To replicate this behaviour you might open a console and do: class Q < ActiveRecord::Base; end; Q.allocate, resulting in #<Q not initialized>. (Do you already love Rails as I do?)
To fix this, either explicitly specify class User < A::Base or rename Base to MyBase. Sorry for suggesting that.

Rails: Subclass not registering as instance of parent class

In a Rails app I am working in, I have code like the following:
# app/models/a.rb
class A < ActiveRecord::Base; ...; end
# app/models/b.rb
class B < A; ...; end
# /app/elsewhere...
do_case(B.new)
def do_case(letter)
case letter
when A
"not nil"
end
end
When I run this locally it performs as expected and I get "not nil" as the return value. But I get nil in my test environment.
The test environment shows that A is among the ancestors (B.ancestors) of letter (B.new), but fails to register as a subclass with the case equality operator or is_a?(A).
A === B.new #=> false in test environment, true locally
B.new.is_a?(A) #=> false in test environment, true locally
This seems like a Rails autoloading issue, but I'm not sure why these methods would be failing if the parent class is included in the ancestors in both environments. What's going on?
Agree, this looks like an autoloading issue. While not ideal, you can resolve it by adding a require_dependency call in B.
As far as where to look for the root issue, it's possible that you have some library dependencies in your test environment that don't exist on Test. I would load a REPL on Test and inspect A. Some useful methods:
class A
def self.what_is_my_nesting
Module.nesting
end
end
A.what_is_my_nesting
A.ancestors
B.ancestors
etc

Rails 3.2.21 and Ruby 2.2.0 breaking Time.zone.parse

With Rails 3.2.21 and Ruby 2.2.0p0 the time zone parser is broken. With Ruby 2.1.2 this was working just fine.
[1] pry(main)> Time.zone.parse("2015-01-12")
NoMethodError: undefined method `year' for nil:NilClass
from /Users/user/.rvm/gems/ruby-2.2.0/gems/activesupport-3.2.21/lib/active_support/values/time_zone.rb:275:in `parse'
Now I know that you can replace it with Time.parse("2015-01-12").localtime but this breaks functionality in my apps. Are there any known fixes for this?
TLDR: rails bug that has been fixed, first fixed version on the 3.2 branch is 3.2.22
Ruby 2.2 changes how default arguments are resolved when there is a name ambiguity:
def now
...
end
def foo(now = now)
end
In older versions of ruby calling foo with no arguments results in the argument now Being set to whatever the now() method calls. In ruby 2.2 it would instead be set to nil (and you get a warning about a circular reference)
You can resolve the ambiguity by doing either
def foo(now = now())
end
Or
def foo(something = now)
end
(And obviously changing uses of that argument)
Apparently the way it used to work was a bug all along. Rails had a few places where this bad behaviour was relied on, including in AS::Timezone.parse. The fix was backported to the 3-2-stable branch and eventually released as part of 3.2.22.
The commit to rails master fixing the issue has a link to the ruby bug filed about this
So, this was your original situation:
class Dog
def do_stuff(x, y=2)
puts x + y
end
end
d = Dog.new
d.do_stuff(1)
--output:--
3
But, the code for do_stuff() has changed, and now you are faced with something similar to this:
class Dog
def do_stuff(x, y=nil)
puts x + y
end
end
d = Dog.new
d.do_stuff(1)
--output:--
1.rb:4:in `+': nil can't be coerced into Fixnum (TypeError)
from 1.rb:4:in `do_stuff'
from 1.rb:10:in `<main>'
Blasted !##$!##$!##$!##$!#### developers!!!
In ruby, you can use alias_method() to create an additional name for a method:
class Dog #Reopen the previously defined Dog class.
alias_method :orig_do_stuff, :do_stuff #Create additional name for do_stuff()
#Now redefine do_stuff():
def do_stuff(x, y=2) #Use a better default value for y.
orig_do_stuff(x, y) #Call the original method.
end
end
d.do_stuff(1)
--output:--
3
According to the rails docs, Time.zone() returns a TimeZone object, so that's the class that defines parse(), which is the method you want to alias. So, the code would look like this:
class Timezone #Re-open the Timezone class.
alias_method :orig_parse, :parse #Create an additional name for parse().
def parse(str, now=now()) #Now, redefine parse().
orig_parse(str, now) #Call the original parse() method.
end
end
Then, you can call parse() like you always have:
Time.zone.parse("2015-01-12")
I guess you should put the code inside app/helpers/application_helper.rb. See if that works.
I think the above would be considered an Adapter pattern. Although, because the plug already fits--it just does't produce the results you want--it might be considered a Decorator pattern. So, now you can put on your resume that you use custom Adapter and/or Decorator patterns in your rails code. :)
Extending 7stud's answer, since parse method is defined in ActiveSupport::TimeZone class, you can simply open it and make this slight change accordingly.
You may place this under config/initializers/timezone.rb or refer this question for alternative places to pace this.
class ActiveSupport::TimeZone
alias_method :orig_parse, :parse #Create an additional name for parse().
def parse(str, now=now()) #Now, redefine parse().
orig_parse(str, now) #Call the original parse() method.
end
end

Is there a way to get a collection of all the Models in your Rails app?

Is there a way that you can get a collection of all of the Models in your Rails app?
Basically, can I do the likes of: -
Models.each do |model|
puts model.class.name
end
The whole answer for Rails 3, 4 and 5 is:
If cache_classes is off (by default it's off in development, but on in production):
Rails.application.eager_load!
Then:
ActiveRecord::Base.descendants
This makes sure all models in your application, regardless of where they are, are loaded, and any gems you are using which provide models are also loaded.
This should also work on classes that inherit from ActiveRecord::Base, like ApplicationRecord in Rails 5, and return only that subtree of descendants:
ApplicationRecord.descendants
If you'd like to know more about how this is done, check out ActiveSupport::DescendantsTracker.
Just in case anyone stumbles on this one, I've got another solution, not relying on dir reading or extending the Class class...
ActiveRecord::Base.send :subclasses
This will return an array of classes. So you can then do
ActiveRecord::Base.send(:subclasses).map(&:name)
EDIT: Look at the comments and other answers. There are smarter answers than this one! Or try to improve this one as community wiki.
Models do not register themselves to a master object, so no, Rails does not have the list of models.
But you could still look in the content of the models directory of your application...
Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
# ...
end
EDIT: Another (wild) idea would be to use Ruby reflection to search for every classes that extends ActiveRecord::Base. Don't know how you can list all the classes though...
EDIT: Just for fun, I found a way to list all classes
Module.constants.select { |c| (eval c).is_a? Class }
EDIT: Finally succeeded in listing all models without looking at directories
Module.constants.select do |constant_name|
constant = eval constant_name
if not constant.nil? and constant.is_a? Class and constant.superclass == ActiveRecord::Base
constant
end
end
If you want to handle derived class too, then you will need to test the whole superclass chain. I did it by adding a method to the Class class:
class Class
def extend?(klass)
not superclass.nil? and ( superclass == klass or superclass.extend? klass )
end
end
def models
Module.constants.select do |constant_name|
constant = eval constant_name
if not constant.nil? and constant.is_a? Class and constant.extend? ActiveRecord::Base
constant
end
end
end
ActiveRecord::Base.connection.tables.map do |model|
model.capitalize.singularize.camelize
end
will return
["Article", "MenuItem", "Post", "ZebraStripePerson"]
Additional information If you want to call a method on the object name without model:string unknown method or variable errors use this
model.classify.constantize.attribute_names
For Rails5 models are now subclasses of ApplicationRecord so to get list of all models in your app you do:
ApplicationRecord.descendants.collect { |type| type.name }
Or shorter:
ApplicationRecord.descendants.collect(&:name)
If you are in dev mode, you will need to eager load models before:
Rails.application.eager_load!
I looked for ways to do this and ended up choosing this way:
in the controller:
#data_tables = ActiveRecord::Base.connection.tables
in the view:
<% #data_tables.each do |dt| %>
<br>
<%= dt %>
<% end %>
<br>
source: http://portfo.li/rails/348561-how-can-one-list-all-database-tables-from-one-project
I think #hnovick's solution is a cool one if you dont have table-less models. This solution would work in development mode as well
My approach is subtly different though -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
classify is well supposed to give you the name of the class from a string properly. safe_constantize ensures that you can turn it into a class safely without throwing an exception. This is needed in case you have database tables which are not models. compact so that any nils in the enumeration are removed.
If you want just the Class names:
ActiveRecord::Base.descendants.map {|f| puts f}
Just run it in Rails console, nothing more. Good luck!
EDIT: #sj26 is right, you need to run this first before you can call descendants:
Rails.application.eager_load!
With Rails 6, Zetiwerk became the default code loader.
For eager loading, try:
Zeitwerk::Loader.eager_load_all
Then
ApplicationRecord.descendants
This seems to work for me:
Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
#models = Object.subclasses_of(ActiveRecord::Base)
Rails only loads models when they are used, so the Dir.glob line "requires" all the files in the models directory.
Once you have the models in an array, you can do what you were thinking (e.g. in view code):
<% #models.each do |v| %>
<li><%= h v.to_s %></li>
<% end %>
On one line: Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
ActiveRecord::Base.connection.tables
Yes there are many ways you can find all model names but what I did in my gem model_info is , it will give you all the models even included in the gems.
array=[], #model_array=[]
Rails.application.eager_load!
array=ActiveRecord::Base.descendants.collect{|x| x.to_s if x.table_exists?}.compact
array.each do |x|
if x.split('::').last.split('_').first != "HABTM"
#model_array.push(x)
end
#model_array.delete('ActiveRecord::SchemaMigration')
end
then simply print this
#model_array
In just one line:
ActiveRecord::Base.subclasses.map(&:name)
I can't comment yet, but I think sj26 answer should be the top answer. Just a hint:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
To avoid pre-load all Rails, you can do this:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency(f) is the same that Rails.application.eager_load! uses. This should avoid already required file errors.
Then you can use all kind of solutions to list AR models, like ActiveRecord::Base.descendants
This works for Rails 3.2.18
Rails.application.eager_load!
def all_models
models = Dir["#{Rails.root}/app/models/**/*.rb"].map do |m|
m.chomp('.rb').camelize.split("::").last
end
end
Dir.foreach("#{Rails.root.to_s}/app/models") do |model_path|
next unless model_path.match(/.rb$/)
model_class = model_path.gsub(/.rb$/, '').classify.constantize
puts model_class
end
This will give to you all the model classes you have on your project.
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
Here's a solution that has been vetted with a complex Rails app (the one powering Square)
def all_models
# must eager load all the classes...
Dir.glob("#{RAILS_ROOT}/app/models/**/*.rb") do |model_path|
begin
require model_path
rescue
# ignore
end
end
# simply return them
ActiveRecord::Base.send(:subclasses)
end
It takes the best parts of the answers in this thread and combines them in the simplest and most thorough solution. This handle cases where your models are in subdirectories, use set_table_name etc.
Just came across this one, as I need to print all models with their attributes(built on #Aditya Sanghi's comment):
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact.each{ |model| print "\n\n"+model.name; model.new.attributes.each{|a,b| print "\n#{a}"}}
This worked for me. Special thanks to all the posts above. This should return a collection of all your models.
models = []
Dir.glob("#{Rails.root}/app/models/**/*.rb") do |model_path|
temp = model_path.split(/\/models\//)
models.push temp.last.gsub(/\.rb$/, '').camelize.constantize rescue nil
end
I've tried so many of these answers unsuccessfully in Rails 4 (wow they changed a thing or two for god sakes) I decided to add my own. The ones that called ActiveRecord::Base.connection and pulled the table names worked but didn't get the result I wanted because I've hidden some models (in a folder inside of app/models/) that I didn't want to delete:
def list_models
Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end
I put that in an initializer and can call it from anywhere. Prevents unnecessary mouse-usage.
The Rails implements the method descendants, but models not necessarily ever inherits from ActiveRecord::Base, for example, the class that includes the module ActiveModel::Model will have the same behavior as a model, just doesn't will be linked to a table.
So complementing what says the colleagues above, the slightest effort would do this:
Monkey Patch of class Class of the Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
and the method models, including ancestors, as this:
The method Module.constants returns (superficially) a collection of symbols, instead of constants, so, the method Array#select can be substituted like this monkey patch of the Module:
class Module
def demodulize
splitted_trail = self.to_s.split("::")
constant = splitted_trail.last
const_get(constant) if defines?(constant)
end
private :demodulize
def defines? constant, verbose=false
splitted_trail = constant.split("::")
trail_name = splitted_trail.first
begin
trail = const_get(trail_name) if Object.send(:const_defined?, trail_name)
splitted_trail.slice(1, splitted_trail.length - 1).each do |constant_name|
trail = trail.send(:const_defined?, constant_name) ? trail.const_get(constant_name) : nil
end
true if trail
rescue Exception => e
$stderr.puts "Exception recovered when trying to check if the constant \"#{constant}\" is defined: #{e}" if verbose
end unless constant.empty?
end
def has_constants?
true if constants.any?
end
def nestings counted=[], &block
trail = self.to_s
collected = []
recursivityQueue = []
constants.each do |const_name|
const_name = const_name.to_s
const_for_try = "#{trail}::#{const_name}"
constant = const_for_try.constantize
begin
constant_sym = constant.to_s.to_sym
if constant && !counted.include?(constant_sym)
counted << constant_sym
if (constant.is_a?(Module) || constant.is_a?(Class))
value = block_given? ? block.call(constant) : constant
collected << value if value
recursivityQueue.push({
constant: constant,
counted: counted,
block: block
}) if constant.has_constants?
end
end
rescue Exception
end
end
recursivityQueue.each do |data|
collected.concat data[:constant].nestings(data[:counted], &data[:block])
end
collected
end
end
Monkey patch of String.
class String
def constantize
if Module.defines?(self)
Module.const_get self
else
demodulized = self.split("::").last
Module.const_get(demodulized) if Module.defines?(demodulized)
end
end
end
And, finally, the models method
def models
# preload only models
application.config.eager_load_paths = model_eager_load_paths
application.eager_load!
models = Module.nestings do |const|
const if const.is_a?(Class) && const != ActiveRecord::SchemaMigration && (const.extends?(ActiveRecord::Base) || const.include?(ActiveModel::Model))
end
end
private
def application
::Rails.application
end
def model_eager_load_paths
eager_load_paths = application.config.eager_load_paths.collect do |eager_load_path|
model_paths = application.config.paths["app/models"].collect do |model_path|
eager_load_path if Regexp.new("(#{model_path})$").match(eager_load_path)
end
end.flatten.compact
end
Make sure to eager load your app before calling descendants so that all the classes are loaded:
Rails.application.eager_load! unless Rails.application.config.eager_load
ApplicationRecord.descendants.each do |clazz|
# do something with clazz, e.g. User, Event, Attendance, etc.
end
def load_models_in_development
if Rails.env == "development"
load_models_for(Rails.root)
Rails.application.railties.engines.each do |r|
load_models_for(r.root)
end
end
end
def load_models_for(root)
Dir.glob("#{root}/app/models/**/*.rb") do |model_path|
begin
require model_path
rescue
# ignore
end
end
end
can check this
#models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
Assuming all models are in app/models and you have grep & awk on your server (majority of the cases),
# extract lines that match specific string, and print 2nd word of each line
results = `grep -r "< ActiveRecord::Base" app/models/ | awk '{print $2}'`
model_names = results.split("\n")
It it faster than Rails.application.eager_load! or looping through each file with Dir.
EDIT:
The disadvantage of this method is that it misses models that indirectly inherit from ActiveRecord (e.g. FictionalBook < Book). The surest way is Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name), even though it's kinda slow.
I'm just throwing this example here if anyone finds it useful. Solution is based on this answer https://stackoverflow.com/a/10712838/473040.
Let say you have a column public_uid that is used as a primary ID to outside world (you can findjreasons why you would want to do that here)
Now let say you've introduced this field on bunch of existing Models and now you want to regenerate all the records that are not yet set. You can do that like this
# lib/tasks/data_integirity.rake
namespace :di do
namespace :public_uids do
desc "Data Integrity: genereate public_uid for any model record that doesn't have value of public_uid"
task generate: :environment do
Rails.application.eager_load!
ActiveRecord::Base
.descendants
.select {|f| f.attribute_names.include?("public_uid") }
.each do |m|
m.where(public_uid: nil).each { |mi| puts "Generating public_uid for #{m}#id #{mi.id}"; mi.generate_public_uid; mi.save }
end
end
end
end
you can now run rake di:public_uids:generate

Resources