Rails extending models without modifying the base file - ruby-on-rails

I've got two Rails 2.3 applications, we'll call them admin and frontend. In admin I have all my models specified in app/models. In frontend I have those models symlinked. I would like to add frontend specific methods to a model that only show up for the frontend application, and not the admin app.
At first I tried just adding config.autoload_paths += "#{RAILS_ROOT}/app/augments/address.rb" with:
class Address
def hello
"hello world"
end
end
But that just wasn't loaded. Calls to Address.first.hello would be met with undefined method 'hello'.
If I require a file that does this:
Address.class_eval do
def hello
"hello world"
end
end
It is loaded once, and for the first hit in development it works, but all subsequent reloads it fails. This is due to config.cache_classes = false in development.
A semi-working solution is to run that from ApplicationController:
class ApplicationController < ActionController::Base
Address.class_eval do
def hello
"hello world"
end
end
end
Which does reload and works every time in dev andprod, but doesn't work for script/runner or script/console. (If this is the only solution I'm sure we could extract that out into a module and include ModelExtensions in ApplicationController.)
Is there something I can add to environment.rb or an initializer that will get reloaded every time in development?

To extend your class you should use module and include it in your model. Something like this:
module Address
def hello
"hello world"
end
end
This is an old but always interesing article on that argument: http://weblog.jamisbuck.org/2007/1/17/concerns-in-activerecord
To include the module only in frontend you should check if the module exists with:
Class A
include Address if defined? Address
end

Related

Rails creating a simple function to run in console for system wide

I want to do something as simple as:
rails c
> ping
=> pong
So I can write an action like:
def ping
puts "pong"
end
But where do I put it? How do I make it work without having to instantiate a new model? application_helper.rb doesn't work, nor does application_controller.rb
You can create a folder services and in it you create a file ping_service.rb
class PingService
def ping
puts 'pong'
end
end
and then in your console :
rails c
> PingService.new.ping
=> "pong"
In case you would like to run custom methods in Rails specifically, you can define you helper methods in a module in lib directory
# lib/custom_console_methods.rb
module CustomConsoleMethods
def ping
puts 'pong'
end
end
Then in the application.rb file, pass a block to console that includes your module into Rails::ConsoleMethods
# config/application.rb
module YourRailsApp
class Application < Rails::Application
console do
require 'custom_console_methods'
Rails::ConsoleMethods.include(CustomConsoleMethods)
end
end
end
If you would like to run it in system wide, just put the methods in ~/.irbrc file. It gets loaded every time you run irb or rails console
def ping
puts 'pong'
end

Cache an http call results in rails

I am new to rails and get stuck on this problem.
The thing I am trying to do is:
I need to call service A to retrieve an idA and then I use idA to perform other actions. my actions in the controller is something like
class SomeController < ApplicationController
def someAction
idA = getIdAfromServiceA(config)
doSomethingElseToServiceB(idA)
end
end
Since the result from serviceA only depends on config, once the config is loaded, idA should not change. Therefore I want to cache idA.
I tried to use instance variable to cache it, no luck("getIdAfromServiceA is called is printed" on every request)
class SomeController
def getIdAfromServiceA(config)
#IdA ||= getIdAfromServiceAviaHTTP(config)
end
private
def getIdAfromServiceAviaHTTP(config)
puts "getIdAfromServiceAviaHTTP is called"
#make some http call
end
end
I also tried to put it in application.rb to cache it on start up. it shows error: undefined method 'getIdAfromServiceAviaHTTP' for SomeHelper:Module (NoMethodError)
module MyProject
class Application < Rails::Application
config.load_defaults 5.1
require_relative 'relativePathToSomeHelperModule'
config.idA = SomeHelper.getIdAfromServiceAviaHTTP(config)
end
end
So my question is, what's a good way to achieve this ? I've been googling for a while but end up in vain. Could you help me with it ? Thanks !
Create a ruby file in under /config/initializers/ and put that code there.
Option 1:
# config/initializers/ida_from_api.rb
IDA = SomeHelper.getIdAfromServiceAviaHTTP(config)
You can then use IDA through out the application.
Option 2:
# config/initializers/ida_from_api.rb
Rails.configuration.idA = SomeHelper.getIdAfromServiceA(config)
You can then use Rails.configuration.idA through out the application. Check Custom configuration for more details.
FYI - files under initializers loaded at the time of application startup.

Modules as Namespaces In Rails

background
we have this file structure:
app/services/promo/promotion_expire.rb
app/services/payment/code_pen_subscription.rb
and the modules/classes defined below.
promotion_expire.rb
module Promo
class PromotionExpire
end
end
code_pen_subscription.rb
module Payment
class CodePenSubscription
def new_suscription_from_promo
##stuff
Promo::PromotionExpire.new.expire_single(#user, true)
##/stuff
end
end
end
problem
In production only, we got an exception
NameE​rror:​ unin​itial​ized ​const​ant P​aymen​t::Pr​omo::​Promo​tionE​xpire
To fix, we changed Promo::PromotionExpire to ::Promo::PromotionExpire in new_subscription_from_promo, which tells ruby to look from root of module hierarchy. But we don't know why
this only happens in production (autoload path?)
why it only happens in CodePenSubscription, not others, because we use this idiom elsewhere.

Can't access model from inside ActionController method added by a Rails engine

I am developing a Rails engine to be packaged as a gem. In my engine's main module file, I have:
module Auditor
require 'engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
require 'application_controller'
end
module ActionController
module Auditor
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def is_audited
include ActionController::Auditor::InstanceMethods
before_filter :audit_request
end
end
module InstanceMethods
def audit_request
a = AuditorLog.new
a.save!
end
end
end
end
ActionController::Base.send(:include, ActionController::Auditor)
where AuditorLog is a model also provided by the engine. (My intent is to have "is_audited" added to the controllers in an application using this engine which will cause audit logging of the details of the request.)
The problem I have is that when this code gets called from an application where the engine is being used, the AuditorLog model isn't accessible. It looks like Ruby thinks it should be a class in ActionController:
NameError (uninitialized constant
ActionController::Auditor::InstanceMethods::AuditorLog)
rather than a model from my engine.
Can anyone point me in the right direction? This is my first time creating an engine and attempting to package it as a gem; I've searched for examples of this and haven't had much luck. My approach to adding this capability to the ActionController class was based on what mobile_fu does, so please let me know if I'm going about this all wrong.
Use ::AuditorLog to access the ActiveRecord class (unless you have it in a module or namespace, in which case you'll need to include the module name).

Rails 2.3.5: How does one access code inside of lib/directory/file.rb?

I created a file so I can share a method amongst many models in lib/foo/bar_woo.rb. Inside of bar_woo.rb I defined the following:
module BarWoo
def hello
puts "hello"
end
end
Then in my model I'm doing something like:
def MyModel < ActiveRecord::Base
include Foo::BarWoo
def some_method
Foo::BarWoo.hello
end
end
The interpreter is complaining that it expected bar_woo.rb to define Foo::BarWoo.
The Agile Web Development with Rails book states that if files contain classes or modules and the files are named using the lowercase form of the class or module name, then Rails will load the file automatically. I didn't require it because of this.
What is the correct way to define the code and what is the right way to call it in my model?
You might want to try:
module Foo
module BarWoo
def hello
puts "hello"
end
end
end
Also for calling you won't call it with Foo::BarWhoo.hello - that would have to make it a class method. However includeing the module should enable you to call it with just hello.
Files in subdirectories of /lib are not automatically require'd by default. The cleanest way to handle this is to add a new initializer under config/initializers that loads your library module for you.
In: config/initializers/load_my_libraries.rb Pick whatever name you want.
require(File.join(RAILS_ROOT, "lib", "foo", "bar_woo"))
Once it has been require'd, you should be able to include it at will.
The issue is twofold.
You need to use the outer Foo scope to define BarWoo
You have defined hello as an instance method, then tried to call it on the class.
Define your method using def self.hello instead of def hello
module Foo
module BarWoo
def self.hello
puts "hello"
end
end
end
You can also do
module Foo::Barwoo; end;

Resources