I'm a newbie to rails. I have created a reports module for a particular project. Now, we want to make it generic across all project like a reports gem. My question is not about how to create & use gem. My questions is "how to make a generic reports lib". For eg. I have a helper module in reports,
module Libquery
module Helper
include QueryConstants(which is dynamic - based on the project)
#methods
end
end
end
My approach: each project will include LibQuery::Helper and also it will include its own constants file.
module ProjectX
module Query
module Helper
include Libquery::Helper
#nothing - inherit all helper methods in libquery
end
end
end
But I'm wondering if that's the most elegant way of doing things ? Or any better way to do it?
First of all, all modules must be capitalized:
module MyModuleName
Second, to use a lib it's best to include it in autoload_paths (in your application.rb file) like this
config.autoload_paths += %W(#{Rails.root}/lib/my_shared_libs)
This means rails will load it automatically, and you'll have available 'out of the box'.
Third, external modules shouldn't depend on project-based modules and classes, since the whole point is to make them easily reusable.
So it boils down to this:
#/lib/my_shared_libs/fun_things.rb
module FunThings
... your code
def laugh
puts 'haha'
end
end
#/models/user.rb
class User
include FunThings
end
User.new.laugh # => 'haha'
Related
This question may just be for Ruby, but it was from working on a Rails that spurred me asking this question.
Suppose I am creating a new module so that I can better organize correlated/coupled code. Let's call this module amazing_feature and all of it's classes/submodules are located in the app/services directory. So according to code loading principles, the entire module should be in the app/services/amazing_feature directory in order to be loaded properly.
Let's say that I have two classes for this module:
# app/services/amazing_feature/thing_one.rb
module AmazingFeature
class ThingOne
...
end
end
# app/services/amazing_feature/thing_two.rb
module AmazingFeature
module ThingTwo
...
end
end
There are some constants that I would like to be available for all of the classes/submodules within module AmazingFeature, as well as being available from the AmazingModule namespace for any external code (eg, other controllers and models, in the Rails point of view). For example, if I want to define MY_CONSTANT = 1, then it would be accessible as just MY_CONSTANT within the module and as AmazingFeature::MY_CONSTANT from outside the module.
So the question is, how can I actually accomplish this in Ruby or Rails? There are thoughts that I've had, approaches that think may work, or approaches that I have seen elsewhere, such as other SOF posts:
Make a file directly in app/services for the module that associate the constants directly to the module. I don't prefer this approach because it feels weird putting a file coupled to the module outside of its subdirectory.
# app/services/amazing_feature.rb
module AmazingFeature
MY_CONSTANT = 1
end
Load the constants globally as a Rails initializer (ie, in config/initializers). I also have the same dislike for this approach as above.
Create a Constants module in the subdirectory so that the constants are colocated with all other code for the module. I just don't know how to properly associate these constants to the parent module, so there is a missing piece in this code example.
# app/services/amazing_feature/constants.rb
module AmazingFeature
module Constants
MY_CONSTANT = 1
end
end
# Now what??? :(
Some other approach? I'm at a loss here.
Thank you.
You can do whatever you want of course, but take some inspiration from popular gems:
https://github.com/rails/rails/blob/master/activerecord/lib/active_record.rb
https://github.com/sparklemotion/nokogiri/blob/master/lib/nokogiri.rb
https://github.com/heartcombo/devise/blob/master/lib/devise.rb
It is normal to rely on Ruby autoloaders to map the names of your constants (AmazingFeature) to file names that contain those constants. So AmazingFeature could map to load/path/amazing_feature.rb and AmazingFeature::Greatness could map to load/path/amazing_feature/greatness.rb.
I'd advise doing this and pretty soon it won't feel weird :)
I think this is a good idea:
# app/services/amazing_feature/constants.rb
module AmazingFeature
module Constants
MY_CONSTANT = 1
end
include AmazingFeature::Constants
end
module AmazingModule
include AmazingFeature::Constants
end
# Then
AmazingFeature::MY_CONSTANT # => 1
AmazingModule::MY_CONSTANT # => 1
Could you please help me?
I am developing gem, it has a module:
#cherry/sdk/high_level.rb
module Cherry
module SDK
module HighLevel
autoload :CherryUser, 'cherry/sdk/high_level/user'
autoload :CherryCard, 'cherry/sdk/high_level/card'
end
end
end
Now I use it like this:
require "cherry/sdk/high_level"
user = Cherry::SDK::CherryUser.new
card = Cherry::SDK::CherryCard.new
But I need user to use my gem classes without namespaces, i.e.
require "cherry/sdk/high_level"
user = CherryUser.new
card = CherryCard.new
How can I achieve it?
Also what do you think about autoload?
it was promised to depreciate this ability, but new ruby versions still have autoload method.
Thank you!
You can always include modules to get access to their classes inside current class scope:
module Cherry
module SDK
module HighLevel
class CherryUser
end
end
end
end
# require "cherry/sdk/high_level"
include Cherry::SDK::HighLevel
user = CherryUser.new # => #<Cherry::SDK::HighLevel::CherryUser:0x007f9c09185ab8>
About autoload - it's considered bad practice and you should avoid it. Here is quite good article about this: http://urbanautomaton.com/blog/2013/08/27/rails-autoloading-hell/
I think if you create classes at the top level and inherit them from your namespaced classes, it should work, i.e.
#cherry_user.rb
class CherryUser < Cherry::SDK::HighLevel::CherryUser
end
require 'cherry_user'
CherryUser.new
Otherwise you might need to define the public interface for your methods in the top-level CherryUser class, that will call the methods you need in your SDK (might be better organization-wise to set up your public API like that)
Also a better practice would be to have
#lib/cherry/user.rb
module Cherry
class User < Cherry::SDK::HighLevel::CherryUser
end
end
#lib/cherry.rb
require 'cherry/user'
# in user code
Cherry::User.new
this way bundler will automatically require lib/cherry.rb which will, in turn, load the User code, also there will be less classes added to the global namespace which is generally good
The question here asks how to extract Rails view helper functions into a gem, and the accept answer is pretty good.
I am wondering - how to do the same for Sinatra? I'm making a gem that has a bunch of helper functions defined in a module, and I'd like to make these functions available to Sinatra views. But whatever I try, I cannot seem to access the functions, I just get a undefined local variable or method error.
So far, my gem structure looks like this (other stuff like gemspec omitted):
cool_gem/
lib/
cool_gem/
helper_functions.rb
sinatra.rb
cool_gem.rb
In cool_gem.rb, I have:
if defined?(Sinatra) and Sinatra.respond_to? :register
require 'cool_gem/sinatra'
end
In helper_functions.rb, I have:
module CoolGem
module HelperFunctions
def heading_tag(text)
"<h1>#{text}</h1>"
end
# + many more functions
end
end
In sinatra.rb, I have:
require 'cool_gem/helper_functions'
module CoolGem
module Sinatra
module MyHelpers
include CoolGem::HelperFunctions
end
def self.registered(app)
app.helpers MyHelpers
end
end
end
This doesn't work. Where am I going wrong?
(And in case you're wondering, yes, I need the helper functions in a separate file. I plan to make the gem compatible with Rails as well, so I want to keep the functions isolated/de-coupled if possible).
You’re mainly just missing the call to Sinatra.register (in cool_gem/sinatra.rb):
require 'sinatra/base'
require 'cool_gem/helper_functions'
module CoolGem
# you could just put this directly in the CoolGem module if you wanted,
# rather than have a Sinatra sub-module
module Sinatra
def self.registered(app)
#no need to create another module here
app.helpers CoolGem::HelperFunctions
end
end
end
# this is what you're missing:
Sinatra.register CoolGem::Sinatra
Now any classic style Sinatra app that requires cool_gem will have the helpers available. If you use the modular style you’ll also need to call register CoolGem::Sinatra inside the Sinatra::Base subclass.
In this case, if you are just providing some helper methods, an easier way might be to just use the helpers method (again in cool_gem/sinatra.rb):
require 'sinatra/base'
require 'cool_gem/helper_functions'
Sinatra.helpers CoolGem::HelperFunctions
Now the methods will be available in classic style apps, and modular style apps will need to call helpers CoolGem::HelperFunctions. This is a bit simpler, but if you are adding methods to the DSL context you will need to use registered as above.
I have written a small module lib/encryption/encryption.rb
module Encryption
def self.encrypt(value)
...
end
def self.decrypt(value)
...
end
end
I want to use/access this module in these two files from Devise, namely:
token_authenticatable.rb
authenticatable.rb
I already have overwritten both of them by creating 2 new files and putting them into /config/initilaizers (copied the original source code within them and modified them)
/config/initializers/token_authenticable.rb
/config/initializers/authenticatable.rb
One file looks like this for instance:
require 'devise/strategies/token_authenticatable'
require './lib/encryption/encryption.rb' #TRIED THIS, BUT DOES NOT WORK
module Devise
module Models
# The TokenAuthenticatable module is responsible for generating an authentication token and
# validating the authenticity of the same while signing in.
...
my modifications work, but how can I access my lib/Encryption.rb module within these files?
Is this modification approach best practice?
If not, what is the right approach?
If you have this in your application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Then '/lib' will be autoloaded. Meaning you can call
require 'encryption/encryption'
And it should work.
Wrap two methods inside a class such as MyEncryptionAlgo. Create an object of that class
obj = Encryption::MyEncryptionAlgo.new
Use this object to access those two method.
obj.encrypt(value)
obj.decrypt(value)
I am using a plugin in Rails, and I call its methods without problems:
plugin_module::class_inside_module.method_a(...)
I want to re-open the class_inside_module and add a new method, I tried in many different ways. I can't figure out why in this way doesn't work:
class plugin_module::class_inside_module
def new_method
puts 'new method'
end
end
I get the error: uninitialized constant plugin_module, but how is possible if I can call without problem plugin_module::class_inside_module.any_methods ?
Do you know why I get that error ? why "uninitialized constant" ? (it is a class declaration :-O )
Do you have any ideas how I can add a new methods in a class inside a module (that is part of a plugin) ?
Thank you,
Alessandro
If you have written your class and module-names like you did, so plugin_module instead of PluginModule this is against ruby/rails standards, and rails will not be able to automatically find the class and module.
If you write something like
module MyModule
class MyClass
end
end
Rails will expect this file to be located in lib\my_module\my_class.
But this can always easily be overwritten by explicitly doing a require.
So in your case, when you write
module plugin_module::class_inside_module
Rails will not know where to find the module plugin_module.
This way of writing only works if module plugin_module is previously defined (and loaded).
So either add the correct require, or rename your modules to standard rails naming, or write it as follows:
module plugin_module
class class_inside_module
This way will also work, because now the order no longer matters.
If the module is not known yet, this will define the module as well.
Either you are re-opening the class, or you define it first (and the actual definition will actually reopen it).
Hope this helps.
Have you tried reopening the module that's wrapping the class, rather than relying on ::?
module plugin_module
class class_inside_module
def new_method
puts 'new_method'
end
end
end
By the way, you know that the proper name for modules and classes is use CamelCase with a capital first letter?
module PluginModule
class ClassInsideModule
def new_method
puts 'new_method'
end
end
end