I want to add a method to the Array class in a rails app. Where should I put this method?
EDIT to be clearer, obviously I put it in a file somewhere, but how do I tell the rails app about where to find it?
One way to do this is to create a file at lib/rails_extensions.rb. Then, add your extensions like so:
class Array
def bring_me_food
# ...
end
def make_tea
# ...
end
end
class Hash
def rub_my_shoulders
# ...
end
end
Then in config/environment.rb, add this:
require 'rails_extensions'
Your mileage with subservient objects may vary.
By default, when you call "require", Rails will look in (from the Rails edge source):
app
app/metal
app/models
app/controllers
app/helpers
app/services
lib
vendor
For simplicity's sake, put the file in lib/, and require it by name in your config/environment.rb, or you can put it in config/initializers/array_extension.rb, and it'll be automatically loaded.
Where I work, we've put all of our extensions to the core Ruby library into a plugin, and stored it in (Rails.root/)vendor/plugins/utilities/lib/core_ext, and then we require the individual extensions in the plugin's init.rb.
Another way to skin this cat: if you say, want to store your core extensions in Rails.root/core_ext, then you can add that path as a load path in your configuration block in environment.rb:
Rails::Initializer.run do |config|
config.load_paths << 'core_ext'
end
Then you can call "require 'array_extension'" from anywhere, and it'll load.
Just put it in a new file, e.g. array_extended.rb
class Array
def my_new_method()
...
end
end
After that you may include this file with require "array_extended.rb".
Be sure you don't override already existing methods as this may break other functionality.
Related
in Rails 5.0.1 application I have file app/actions/frontend/cart/get_cart_items_summarized.rb with:
module Actions
module Frontend
module Cart
class GetCartItemsSummarized
#content here
end
end
end
end
And in app/helpers/application_helper.rb I call it:
def get_cart_items
#...
items = Actions::Frontend::Cart::GetCartItemsSummarized.new.call
#...
end
But I'm getting:
uninitialized constant ApplicationHelper::Actions
Why? How should I use this class?
Thanks
In rails/autoloading, the first level of directories, the one directly under app, is not considered a part of the name. It is so that your model can be just User and not Models::User, etc.
My solution to this is to put all custom stuff in app/lib. This way, lib eats that non-naming layer and the rest of your folder structure become a name. In your example, put your file to
app/lib/actions/frontend/cart/get_cart_items_summarized.rb
Of course, feel free to replace "lib" with whatever you want ("app/custom", for example). This name doesn't matter.
Either use a full-qualified name:
::Actions::Frontend::Cart::GetCartItemsSummarized.new.call
or just stick to Rails constant lookup (the below should work):
GetCartItemsSummarized.new.call
In "config/application.rb", add "app" to autoload paths, e.g.:
class Application < Rails::Application
config.autoload_paths += Dir[Rails.root.join('app')]
end
I am using a gem in my rails app, and there is a method that I would like to override. The gam is authlogic, and the specific method I was to override is find_by_smart_case_login_field(login).
I made a file in lib/modules with the following code:
# lib/modules/login.rb
module Authlogic
module ActsAsAuthentic
module Login
module Config
def find_by_smart_case_login_field(login)
login = login.downcase unless validates_uniqueness_of_login_field_options[:case_sensitive]
if login_field
where({ login_field.to_sym => login })
else
where({ email_field.to_sym => login })
end
end
end
end
end
end
But this didn't do anything. Does anyone know how to overwrite the above method?
Well, you are monkey patching a gem. Not bad, just don't abuse it:)
Two things you need to do before making your monkey patching works.
Add /lib to auto load path otherwise Rails don't know it.
In config/application.rb, find the autoload_path line, change it to
config.autoload_paths += %W(#{config.root}/extras #{config.root}/lib)
Require your custom module at app loading.
In config/initializers, add a custom file say application.rb, then add the following line
require 'modules/login.rb'
# Pay attention: No "lib/" before the file path
Now, profit!
As to module path, it doesn't matter as long as your module nesting is correct in the file.
I'm going out on a limb here, but my guess would be that you'd have to name the file something like
lib/authlogic/acts_as_authentic/login/config.rb
In other words, I believe the path has to map to the module structure.
I'm making a custom generator that generates a new rails app, and I do it like this
require 'thor'
require 'rails/generators/rails/app/app_generator'
class AppBuilder < Rails::AppBuilder
include Thor::Actions
include Thor::Shell
...
end
The problem is, how do I add a new source directory (which is then used by Thor::Actions#copy_file, Thor::Actions#template, and the others)? I saw in the Thor's documentation that Thor::Actions#source_paths holds the sources (it's an array of paths), so I tried overriding it inside my class (since I've included Thor::Actions):
def source_paths
[File.join(File.expand_path(File.dirname(__FILE__)), "templates")] + super
end
With this I wanted to add the ./templates directory in the sources, while still keeping the Rails' one (that's why the + super at the end). But it doesn't work, it still lists the Rails' source path as the only one.
I tried browsing through the Rails' source code, but I couldn't find how Rails put his directory in the source paths. And I really want to know that :)
This worked:
require 'thor'
require 'rails/generators/rails/app/app_generator'
module Thor::Actions
def source_paths
[MY_TEMPLATES]
end
end
class AppBuilder < Rails::AppBuilder
...
end
I don't understand why, but I've spent too much time on this already, so I don't care.
Thor will access your source_paths method and add these to the defaults:
# Returns the source paths in the following order:
#
# 1) This class source paths
# 2) Source root
# 3) Parents source paths
#
def source_paths_for_search
paths = []
paths += self.source_paths
paths << self.source_root if self.source_root
paths += from_superclass(:source_paths, [])
paths
end
So all you need to do in your class is:
class NewgemGenerator < Thor::Group
include Thor::Actions
def source_paths
['/whatever', './templates']
end
end
Hope this helps :)
The source_paths method doesn't work when using AppBuilder. (which is another option to using rails templates). I have a files directory next to the app_builder.rb file that this class is in. I have this working, though it seems there should still be a more elegant way.
tree .
|-- app_builder.rb
|-- files
`-- Gemfile
class AppBuilder < Rails::AppBuilder
def initialize generator
super generator
path = File.expand_path( File.join( '..', File.dirname( __FILE__ )) )
source_paths << path
end
def gemfile
copy_file 'files/Gemfile', 'Gemfile'
end
and then on the console:
rails new my_app -b path_to_app_builder.rb
The dots are required since the ruby file 'app_builder.rb' is slurped up and eval'd after the rails new command changes into the new app directory (I think).
It's an old post, however, the problem remains the same, I spent time to figure that out. I let a comment here if it can help.
Rails add by default the path lib/templates so you can custom any templates by copying them in this directory.
Check out the correct directories structures https://github.com/rails/rails/tree/v6.0.1/railties/lib/rails/generators/rails
There is subtle though and is not so obvious to catch.
Take for instance the helper generator, as mentioned in the official Rails documentation, the structure is
https://github.com/rails/rails/tree/v6.0.1/railties/lib/rails/generators/rails/helper
- helper
- templates
- helper.rb.tt
- helper_generator.rb
Here, what Railties expects to find in your project:
- lib
- templates
- helper
- helper.rb
your helper.rb is a copy of helper.rb.tt
the last thing, if you plan to use it in Rails::Engine you have to tell it to load that path
# lib/blorgh/engine.rb
module Blorgh
class Engine < ::Rails::Engine
isolate_namespace Blorgh
config.generators.templates << File.expand_path('../templates', __dir__)
end
end
Really hope that can help and save time for others.
https://github.com/rails/rails/blob/v6.0.1/railties/lib/rails/application/configuration.rb#L188
https://guides.rubyonrails.org/generators.html#customizing-your-workflow-by-changing-generators-templates
https://github.com/rails/rails/blob/v6.0.1/railties/CHANGELOG.md
https://guides.rubyonrails.org/engines.html
Setting
In my gem there is a Component base class:
module Component
class Base
def self.inherited(component_class)
Component.class_eval do
define_method(component_class.to_s.underscore) do |*args, &block|
component_class.new(*args, &block)
end
end
end
end
end
For every class inheriting from this base class (e.g. FancyComponent < Component::Base), a helper should be defined in the Component module (e.g. fancy_component).
This does work for any components delivered with my gem, i.e. Component.instance_methods.include? :fancy_component #=> true
Now Rails comes into play
I want users of my gem to be able to add components. These are stored in app/components.
This folder is included in all of the following:
MyApp::Application.config.load_paths
MyApp::Application.config.autoload_paths
MyApp::Application.config.eager_load_paths
A new component UserComponent < Component::Baseis stored in app/components/user_component.rb.
The Problem
If I launch the rails console, the situation is as follows:
Loading development environment (Rails 3.0.4, ruby-1.9.2-p0)
Component.instance_methods.include? :fancy_component #=> true
Component.instance_methods.include? :user_component #=> false
UserComponent #=> UserComponent
Component.instance_methods.include? :user_component #=> true
So the helper method is not available until the component class is somehow accessed.
So how to force eager loading of that class so that inherited is executed?
Your idea is nice but I'd greatly advise you against implementing something like this, `cos this would usually be done by pre-loading the models before Rails takes notice of them and this could lead to hard to figure loading issues in your app (like classes that should have been re-loaded but were not).
Here's a basic example, of the ways for you to implement this feature would be, at your "root" gem file (if your gem is named "my_component", the "lib/my_component.rb" file) do something like this:
require 'component/base/component'
# require here all other classes necessary for your gem
rb_files = File.join( Rails.root, 'app', 'components', '**', '*.rb' )
Dir.glob( rb_files ).each do |file|
require file.gsub('.rb')
end
With this you'd load all files under "app/components" but then Rails would not reload these objects, as they were not required by Rails, but by your own gem. If you don't mind not being able to change these files, this might be ok, but then you could have issues in the development environment (or any environment that has "cache_classes" set to false).
This is a bug in rails (but wontfix), see also this ticket here:
https://github.com/rails/rails/issues/3364
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.