Rails /lib modules and - ruby-on-rails

I am writing a custom wrapper for open_flash_chart plugin. It's placed in /lib and load it as a module in ApplicationController.
However, I have either a problem with the Class hierarchy or some other problem.
From any controller I can access open_flash_chart functions as OpenFlashChart, Line etc.
However, in a class in a /lib module, it doesnt work!
Any ideas?

There are two ways that files get loaded in Rails:
It is registered in the autoload process, and you reference a constant that corresponds to the file name. For instance, if you have app/controllers/pages_controller.rb and reference PagesController, app/controllers/pages_controller.rb will automatically be loaded. This happens for a preset list of directories in the load path. This is a feature of Rails, and is not part of the normal Ruby load process.
Files are explicitly required. If a file is required, Ruby looks through the entire list of paths in your load paths, and find the first case where the file you required is in the load path. You can see the entire load path by inspecting $LOAD_PATH (an alias for $:).
Since lib is in your load path, you have two options: either name your files with the same names as the constants, so Rails will automatically pick them up when you reference the constant in question, or explicitly require the module.
I also notice that you might be confused about another thing. ApplicationController is not the root object in the system. Observe:
module MyModule
def im_awesome
puts "#{self} is so awesome"
end
end
class ApplicationController < ActionController::Base
include MyModule
end
class AnotherClass
end
AnotherClass.new.im_awesome
# NoMethodError: undefined method `im_awesome' for #<AnotherClass:0x101208ad0>
You will need to include the module into whatever class you want to use it in.
class AnotherClass
include MyModule
end
AnotherClass.new.im_awesome
# AnotherClass is so awesome
Of course, in order to be able to include the module in the first place, you'll need to have it available (using either of the techniques above).

In Rails 3 /lib modules are not loaded automatically.
This is because the line:
# config.autoload_paths += %W(#{config.root}/extras)
inside config/application.rb is commented.
You can try to uncomment this line or, (it worked even better for me), leave this commented (for future reference) and add this two lines:
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]

What worked for me, besides uncommenting config.autoload_paths (I’m on Rails 3.1.3), was to create a initializer like this:
#config/initializers/myapp_init.rb
require 'my_module'
include MyModule
This way I can call mymodule methods from anywhere and as class methods Model.mymodule_method or as instance methods mymodel.mymodule_method
Maybe some expert may explain the implications of this. By now, use it at your own risk.
Edit: Afterwards, I think a better approuch would be:
create a initializer like this:
#config/initializers/myapp_init.rb
require ‘my_module’
Include the module where needed, like this:
1) if you want to use it as "Class Methods" use "extend":
class Myclass < ActiveRecord::Base
extend MyModule
def self.method1
Myclass.my_module_method
end
end
2) if you want to use it as "Instance Methods" include it inside Class definition:
class Myclass < ActiveRecord::Base
include MyModule
def method1
self.my_module_method
end
end
3) remember that include MyModule refers to a file my_module.rb in your load path that must be required first

To use the module lib/my_module.rb in your models and controllers:
In config/application.rb:
config.watchable_dirs['lib'] = [:rb]
In your model (similar idea for your controller):
require_dependency 'my_module'
class MyModel < ActiveRecord::Base
include MyModule
MyModule.some_method
end
This method is described in more detail at http://hakunin.com/rails3-load-paths

It might be the case that you want to explicitly load file(s) under lib directory at time of application initialization. In my config/application.rb, I have an entry as, config.autoload_paths += %W(#{config.root}/lib) Also this might be the case that module name/hierarchy is not same as it is in file or location/name of file is not same as that hierarchy, so auto-load of that file is also not possible. So when I added an entry at bottom of config/application.rb as, require "./lib/file_name_without_extention it worked fine.

Related

Accessing helpers and models from rails engine initializer

I'm trying to make a Ruby on Rails engine, and I want the initializer to be able to have access to the helpers and models.
I'll write below an example, part of the code, and the error that I have. It may not be the recommended way, because I can see that in some cases I'm repeating myself, but it's the first engine I make.
file lib/my_engine/engine.rb
module MyEngine
require 'my_engine/functions'
class Engine < ::Rails::Engine
isolate_namespace MyEngine
config.autoload_paths += %W( #{config.root}/lib )
end
class GlobalVars
attr_accessor :foo
def initialize
#foo = MyEngine::Functions.new
end
end
class << self
mattr_accessor :GLOBAL
mattr_accessor :USER_CONFIG
self.GLOBAL = MyEngine::GlobalVars.new
# add default values of more config vars here
self.USER_CONFIG = 'default config'
end
def self.setup(&block)
yield self
end
end
file lib/my_engine/functions.rb
module MyEngine
require '../../app/helpers/my_engine/options_helper'
class Functions
include MyEngine::OptionsHelper
attr_accessor :some_link
def initialize
#some_link = get_option('dummy')
end
end
end
There is also a controller named OptionsController in app/controllers/my_engine, and OptionsHelper in app/helpers/my_engine/options_helper.rb:
module MyEngine
module OptionsHelper
def get_option(name)
MyEngine::Option.new
end
end
end
When I try to run the dummy application, this error occurs:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
If I change to just Option.new, I have this error:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::OptionsHelper::Option (NameError)
For ::MyEngine::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
For ::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant Option (NameError)
The dummy application has nothing in it. All helpers and models defined above are in the engine.
Before this, I had other errors because it couldn't access the helper, or the Functions class. I had to add require and include to make it work even if they are placed in the same directory. Also, to work, I had to move GlobalVars from its own file inside engine.rb.
Can somebody show me what I'm doing wrong?
After I used required for every class, I ended with ActiveRecord::ConnectionNotEstablished, and it seems that not everything is loaded and available at that point when the GLOBAL object is created.
So I moved the code that was using the models in a separate init method. Then, I added an after initialize event:
config.after_initialize do
MyEngine.GLOBAL.init
end
I see a possible problem: because you are inside module MyEngine it might be possible that actually rails is looking for MyEngine::MyEngine::Option, so I see two approaches:
just write Option: this will look for MyEngine::Option
write ::MyEngine::Option this will look in the global namespace and find MyEngine::Option
Secondly, if that does not help, even though your path seems correct, but you can always explicitly require "my_engine/option" at the top of the file. I am not entirely sure the autoloading in an engine works in quite the same way, and I tend to, in my engine file, require almost everything (to make sure it works).
In my engine.rb I do
require_relative '../../app/models/my_engine/option'
maybe this will help, but it is not a nice solution.

Rails global function available to all objects

I'd like to make a function current_order_week that would be available globally throughout my app and could be called similarly to something like current_user. I don't want to have to include it in a specific model / controller, I just want it available everywhere.
I've modified my /lib folder to include a lib_extensions.rb file and added to that file:
class Object
def current_order_week
end
end
I've modified my application.rb to include:
config.autoload_paths << Rails.root.join('lib')
config.eager_load_paths << Rails.root.join('lib')
But when I attempt to call current_order_week from the console or from a test, I still see:
NameError: undefined local variable or method 'current_order_week'
for main:Object
What else do I need to do?
You should add this function in the application_helper.rb file. All controllers extend from ApplicationController and ApplicationController includes the ApplicationHelper.
module ApplicationHelper
def current_order_week
end
end
This will be available to use in views and controllers
Monkey-patching core classes like Object is usually not a good idea, this may interfere with some gems etc. and in general can lead to painful debugging in the future.
If you absolutely want to do this - autoloading will not pick up Object from lib, because it is already defined. Create an initializer in config/initializers, then it will be loaded, but it will not reload on code changes.
But better way is including such code in ApplicationHelper, ApplicationRecord and ApplicationController
autoload_paths and eager_load_paths don't include modules, they only require files in which modules are defined. To use current_order_week you need to specify the name of the module:
module Foo
def current_order_week
.
.
.
end
end
Foo.current_order_week()
In order to use current_order_week without prepending the name of the module to it, you need to include Foo inside your controllers and models:
class ApplicationController < ActionController::Base
include Foo
def some_action
current_order_week()
end
end
class ApplicationRecord < ActiveRecord::Base
include Foo
end

Rails lib load order

I have the following classes in my applications lib directory:
proxy.rb
class Proxy
end
ga_proxy.rb
class GaProxy < Proxy
include GaProxy::Metrics
end
metrics.rb
class GaProxy
module Metrics
end
end
Load order clearly matters here:
metrics.rb need to be loaded before ga_proxy.rb
proxy.rb needs to be loaded before ga_proxy.rb
But if metrics.rb is loaded before ga_proxy, then I get 'superclass mismatch for class GaProxy' because GaProxy has already been defined without a parent class.
How can I get around this issue?
Thanks
In your application.rb file, specify each file you want to load in order:
config.autoload_paths += %W( #{config.root}/lib/proxy.rb, #{config.root}/lib/metrics.rb, #{config.root}/lib/ga_proxy.rb )
I've never had this issue but why can't you do the following in config/initializers
create a file config/initializers/libs.rb
require 'proxy'
require 'ga_proxy'
require 'metrics'
Generally, in ruby, you require what you need in your file:
# ga_proxy.rb
require './proxy'
require './metrics'
class GaProxy < Proxy
include GaProxy::Metrics
end
That's assuming the files live in the same directory. Of course, Rails does some autoloading magic for you, but you can still be explicit about your requires.
Edit
You'll have to specify the superclass in metrics.rb:
# metrics.rb
class GaProxy < Proxy
module Metrics
end
end

Display a Variable from a Method in a File in the /lib/ directory on a View in Rails

In a file called foo.rb in my /lib/ directory it reads:
module Foo
def some_method
#text_1 = "Hello!"
end
end
How can I get the results of this method to show up in a View?
I've seen that I need to include the following line in the /config/application.rb file:
config.autoload_paths += %W(#{config.root}/lib
However, I do not completely understand how to pass a variable from a module in a file saved in the /lib/ directory - to show up in a View. I appreciate any advice.
In order to get that value to show up in the view, you'll need to understand how modules are used in Ruby. Typically modules are mixed into other classes either by including or extending them. This would then make that method available to another class which could then be referenced in the view. In your case you might want to include it so it becomes available to instances of whatever class you put it in. Say you have an ActiveRecord model called MyClass and you include Foo. You can then call my_method on instances of that model as demonstrated below:
class MyClass < ActiveRecord::Base
include Foo
end
In your controller:
class MyController
def new
#my_class = MyClass.new
end
end
In your view:
#my_class.some_method
Having said all that, it seems like there might be a better way to do whatever it is you're trying to do :)
Yes.I agree with
Beerlington.
You can do it in an other way,
It is not mandatory to add config.autoload_paths += %W(#{config.root}/lib to application file.Because by default the files which are located in /lib directory won't be executed at first when we run an application using rails s.
In order to make those files to be loaded,we need to include that line in application.rb.
Otherwise,we can directly write it as below,
In model,
require 'Filename'
class MyClass < ActiveRecord::Base
include Foo
end
In controller,
require 'foobar'
class BuyerController < ApplicationController
include Foobar
end
In foobar.rb,
module Foobar
def Foobar.foobar
"Hello world!"
end
end
In view,
<%= Foobar.foobar %> (You can directly call the method by using Modulenmae.Methodname)

How can I extend ActiveRecord from app/modules?

I have several different acts_as_... custom class methods I'd like to use in my app. I would like the code for those methods to be in files in the app/modules directory.
I have been unable to get this working.
For instance, I have a file: app/modules/acts_as_lockable
module ActsAsLockable
def acts_as_lockable
before_create :set_lock
include InstanceMethods
end
module InstanceMethods
protected
def set_lock
now = Time.now.to_s
self.lock = Digest::SHA1.hexdigest(now)
end
end
end
ActiveRecord::Base.extend ActsAsLockable
And in application.rb
config.autoload_paths += %W(#{config.root}/app/modules)
When I try to load up a model that calls acts_as_lockable I get the following error:
NameError: undefined local variable or
method `acts_as_lockable'
My guess is that I shouldn't be autoloading the modules folder because ActiveRecord has already been loaded when I extend it? Is there another way to do this? I would like to be able to alter the file during development without restarting my server but that's more of a want that a need.
I think you're thinking about this in the wrong way.
You are adding this module to the load path,
but it will only load if you either say;
require 'acts_as_lockable'
or
ActsAsLockable
I'd suggest you never really want to say either of these inside your code.
The correct paradigm you're looking for is an "initializer".
I suggest you create a file called "config/initializers/acts_as_lockable.rb"
In this file you can either include the whole code,
or just include a require 'acts_as_lockable'
Normally I keep things like this inside the libs directory
ensure lib is in the load path
** config/application.rb **
config.autoload_paths += %W(#{config.root}/lib)
** lib/acts_as_lockable.rb **
module ActsAsLockable
def acts_as_lockable
before_create :set_lock
include InstanceMethods
end
module InstanceMethods
protected
def set_lock
now = Time.now.to_s
self.lock = Digest::SHA1.hexdigest(now)
end
end
end
then in the initializer
** config/initializers/acts_as_lockable.rb **
require 'acts_as_lockable'
ActiveRecord::Base.extend ActsAsLockable
The problem is that ruby autoload mechanism is a lazy process: When a constant like ActsAsLockable is used within your code, it looks for a file called acts_as_lockable.rb within the autoload_paths. As You never actually use ActsAsLockable, the file never gets loaded. You could do (although not tremendously beautiful):
ActsAsLockable
class MyModel < ActiveRecord::Base
acts_as_lockable
...
end
I think the acts_as_* pattern is ment to be used be plugins and gems to easily integrate functionality into your code. Plugins and gems are supposed to be in a final state when you integrate them into your project so you would not need the reloading functionality for the development mode.
I hope this helps.

Resources