How can the Rails version be checked when a class loads? - ruby-on-rails

I'm backporting an ActiveSupport method from Rails 5 into a class within a Rails 4 app. There won't be a naming collision since the class name will be different, but I would like to warn or raise an exception when the class is loaded in Rails 5 (after an upgrade) to remind us to remove the code. Is it possible to do that?

I suppose it depends on how you're loading the class. But, to check a version, see https://github.com/rails/rails/blob/master/version.rb — Rails::VERSION::MAJOR will be your friend here.
What I would do is hook into included
class Tracked
def self.tracked
#tracked ||= []
end
end
module Track
def self.included(base)
puts "Woohoo I am included."
if Rails::VERSION::MAJOR == 5
puts "yeah, 5"
else
puts "thanks, DHH"
end
Tracked.tracked << base
end
end
class Steve
include Track
end

Related

Rails 5 include helper in class

We are migrating from Rails 4.2 to 5.2. The following code works fine in 4.2 but not in 5
require 'action_view'
module OurModule
class CheckReport
include ActionView::Helpers::DateHelper
def self.our_method
start_time = Time.current
LOGGER.info "OurModule::CheckReport.our_method finished in #{distance_of_time_in_words(start_time, Time.current)}"
end
end
end
But in Rails 5 we are getting:
NoMethodError: undefined method `distance_of_time_in_words' for OurModule::CheckReport:Class
This seems to be because these are class methods and not instance methods.
So, why did it work in Rails 4 (same ruby version - 2.4.9) and what can we do to fix it (apart from making all these cases instance methods?)
Seeing as some people are coming here to find the answer, here is what I did:
require 'action_view'
module OurModule
class CheckReport
def self.our_method
start_time = Time.current
LOGGER.info "OurModule::CheckReport.our_method finished in #{ActionController::Base.helpers.distance_of_time_in_words(start_time, Time.current)}"
end
end
end

Ruby - Check if controller defined

I am using Solidus with Ruby on Rails to create a webshop and I have multiple modules for that webshop.
So, I defined a me controller into an module called 'solidus_jwt_auth' with the followin code:
module Spree
module Api
class MeController < Spree::Api::BaseController
def index
...
end
def orders
...
end
def addresses
...
end
end
end
end
I want to extend this in another module called 'solidus_prescriptions' so I created a decorator for this with the following code me_decorator:
if defined? Spree::Api::MeController.class
Spree::Api::MeController.class_eval do
def prescriptions
...
end
def create_prescription
...
end
private
def prescription_params
params.require(:prescription).permit(
*Spree::CustomerPrescription.permitted_attributes
)
end
end
end
And for this I wrote unit tests in solidus_prescription module and integration tests in webshop. The unit tests are working fine, but the integration tests are giving the following error:
Error:
MeEndpointsTest#test_me/prescriptions_post_endpoint_throws_an_error_when_wrong_params:
AbstractController::ActionNotFound: The action 'create_prescription' could not be found for Spree::Api::MeController
test/integration/me_endpoints_test.rb:68:in `block in '
Which means that he can not find the MeController defined in another module. How can I make the check if the MeController is defined since the code bellow does not help me with anything:
if defined? Spree::Api::MeController.class
end
This worked in the end:
def class_defined?(klass)
Object.const_get(klass)
rescue
false
end
if class_defined? 'Spree::Api::MeController'
....
end
if defined? should do exactly what you want it to do in theory. The problem is you're checking if defined? Spree::Api::MeController.class. The #class of your class is Class. So what you're really getting is if defined? Class which will always be true!
This issue is most likely not that the conditional is failing but that it's never getting read. Rails lazy loads most of the code you write, meaning the file is not read until it's called somewhere in execution.
The decorator module should just contain the methods you want to add, without the conditionals or the use of class_eval. Then in the original class you can include it.
module Spree
module Api
class MeController < Spree::Api::BaseController
include MeDecorator
end
end
end
If for any reason you're not certain MeDecorator will be defined, don't use defined?, because defined? MeDecorator will not actually go looking for it if it's not defined and load the necessary file. It will return nil if the constant has no value. Just rescue a NameError
module Spree
module Api
class MeController < Spree::Api::BaseController
begin
include MeDecorator
rescue NameError => e
logger.error e
end
end
end
end

Implementing "after_find" in datamapper

I've taken over porting a large application from rails 2 to rails 3. The new application uses Datamapper as its ORM (decision taken prior to me coming to the project, so I can't change this).
The old application used the activerecord "after_find" callback quite extensively;
class Foo < ActiveRecord::Base
after_find :bar
def bar
#This would be called after an object was "found"
end
end
I need to implement this in Datamapper, but I can't just monkey patch the find methods because datamapper uses composition rather than inheritence;
class Foo
include Datamapper::Resource
end
so if I monkey patched the find methods, there would be no way of me calling the original method! Could anyone point me in the right direction?
== EDIT ==
I have tried including the following:
module ClassMethods
def after_find(meth)
class << self
alias_method :orig_first, :first
def first(*args, &block)
puts "XXX FOUND THE FIRST"
orig_first args, &block
end
end
end
end
But this just sends the code into an infinite loop (constantly printing "XXX FOUND THE FIRST")

Cannot include module in model

I'm using
Ruby version 1.8.7
Rails version 3.0.3
I have a method called alive in every model of my rails app:
def alive
where('deleter is null')
end
I don't want to copy this code in every model so I made a /lib/life_control.rb
module LifeControl
def alive
where('deleter is null')
end
def dead
where('deleter is not null')
end
end
and in my model (for example client.rb) I wrote:
class Client < ActiveRecord::Base
include LifeControl
end
and in my config/enviroment.rb I wrote this line:
require 'lib/life_control'
but now I get a no method error:
NoMethodError in
ClientsController#index
undefined method `alive' for
#<Class:0x10339e938>
app/controllers/clients_controller.rb:10:in
`index'
what am I doing wrong?
include will treat those methods as instance methods, not class methods. What you want to do is this:
module LifeControl
module ClassMethods
def alive
where('deleter is null')
end
def dead
where('deleter is not null')
end
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
This way, alive and dead will be available on the class itself, not instances thereof.
I'm aware this is a pretty old question, the accepted answer did work for me, but that meant me having to re-write a lot of code because i have to change the module to a nested one.
This is what helped me with my situation and should work with most of today's applications.(not sure if it'll work in the ruby/rails version in the question)
instead of doing include use extend
So as per the question, the sample code would look like:
class Client < ActiveRecord::Base
extend LifeControl
end
Just put this line in application.rb file
config.autoload_paths += Dir["#{config.root}/lib/**/"]
Edited:
This line is working fine for me.
I want to suggest one more thing, ruby 1.8.x is not compatible with rails 3.x.
So just update your ruby for version 1.9.2
Following is my POC
In lib folder:
lib/test_lib.rb
module TestLib
def print_sm
puts "Hello World in Lib Directory"
end
end
In model file:
include TestLib
def test_method
print_sm
end
And In application.rb
config.autoload_paths += Dir["#{config.root}/lib/**/"]
Now you can call test_method like this in controller:
ModelName.new.test_method #####Hello World in Lib Directory

Legacy table with column named "class" in Rails

I've got a legacy table that my rails application shares with another application. It has a column called "class". The first time I reference any attribute in that model, I get an error. Subsequent references to attributes work. Is there a good workaround for this, or should I just go modify the other application that uses this table (ugh)?
>> Member::Ssg.find(:first)
=> #<Member::Ssg ssg_key: #<BigDecimal:10b169688,'0.253E3',4(8)>, org_id: 2, academic_year: 2006, class: true, next_due_date: "2011-06-01", submitted_date: "2006-02-13", notes: nil, owner_id: "1">
>> Member::Ssg.find(:first).notes
NoMethodError: undefined method `generated_methods' for true:TrueClass
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/attribute_methods.rb:247:in `method_missing'
from (irb):2
>> Member::Ssg.find(:first).notes
=> nil
SOLUTION:
I went with a combination of the Bellmyer solution and adding the code below to my model
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
NOTE: Please see the updated solution at the end of this answer. Leaving the original outdated solution for historic reasons.
This has come up often enough (legacy column names interfering with ruby/rails) that I might just make a plugin out of this. Here's how you can fix it right away, though. Create this file in your app:
# lib/bellmyer/create_alias.rb
module Bellmyer
module CreateAlias
def self.included(base)
base.extend CreateAliasMethods
end
module CreateAliasMethods
def create_alias old_name, new_name
define_method new_name.to_s do
self.read_attribute old_name.to_s
end
define_method new_name.to_s + "=" do |value|
self.write_attribute old_name.to_s, value
end
end
end
end
end
And now, in your model:
class Member < ActiveRecord::Base
include Bellmyer::CreateAlias
create_alias 'class', 'class_name'
end
The first parameter to create_alias is the old method name, and the second parameter is the new name you want to call it, that won't interfere with rails. It basically uses the read_attribute and write_attribute methods to interact with the column instead of the ruby methods that get defined by ActiveRecord. Just be sure to use the new name for the field everywhere, like so:
member.class_name = 'helper'
This works with ruby 1.8, but I haven't tested with ruby 1.9 yet. I hope this helps!
UPDATE: I've found a better solution that works in Rails 3, the safe_attributes gem. I've written a blog post explaining how to use it, with example code snippets, and a full sample app you can download from github and play around with. Here's the link:
Legacy Database Column Names in Rails 3
The following works in Rails 6.0.2.2
class ReasonCode < ApplicationRecord
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
def as_json(options={})
add_class = attributes.keys.include?('class')
if add_class
if options[:only]
add_class = Array(options[:only]).map(&:to_s).include?('class')
elsif Array(options[:except])
add_class = Array(options[:except]).map(&:to_s).exclude?('class')
end
end
options[:except] = Array(options[:except])
options[:except].push('class')
json = super(options)
json['class'] = attributes['class'] if add_class
json
end
end
Adapted from this answer https://www.ruby-forum.com/t/activerecord-column-with-reserved-name-class/125705/2. The as_json method was added because rendering the record as json gave a SystemStackError (stack level too deep). I followed the serialization code in the Rails repo to only render the class attribute if specified in the as_json options.

Resources