How to require module from gem to use its classes without namespace? - ruby-on-rails

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

Related

Ruby/Rails organizing and loading constants for an entire module

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

Create Generic Module in rails

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'

How do I make functions in a gem available to Sinatra views?

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.

Include or extend a class/module from a gem (e.g. devise)

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)

Rails class loading skips namespaced class when another class of same name in root namespace is loaded

I have two namespaces, each with its own controller and presenter classes:
Member::DocumentsController
Member::DocumentPresenter
Guest::DocumentsController
Guest::DocumentPresenter
Both presenters inherit from ::DocumentPresenter.
Controllers access their respective presenters without namespace specified, e.g.:
class Guest::DocumentsController < ActionController::Base
def show
DocumentPresenter.new(find_document)
end
end
This usually calls presenter within same namespace. However sometimes in development environment I see base ::DocumentPresenter is being used.
I suspect the cause is that base ::DocumentPresenter is already loaded, so Rails class auto-loading doesn't bother to look further. Is this likely the case? Can it happen in production environment too?
I can think of two solutions:
rename base class to DocumentPresenterBase
explicitly require appropriate presenter files in controller files
Is there a better solution?
You are correct in your assumptions - If you do not specify namespace, Ruby starts from current namespace and works its way up to find the class, and because the namespaced class is not autoloaded yet, the ::DocumentPresenter is found and autoloader does not trigger.
As a solution I would recommend renaming ::DocumentPresenter to DocumentPresenterBase, because this protects you from bugs when you forget namespacing or explicit requiring somewhere.
The second option to consider would actually be using specific namespaced classnames all over the place, but this suffers from bugs when you accidentally forget to namespace some call.
class Guest::DocumentsController < ActionController::Base
def show
Guest::DocumentPresenter.new(find_document)
end
end
Third option would be your second - explicitly require all the classes in initializer beforehand. I have done this with Rails API which receives embedded models in JSON and Rails tends to namespace them when the actual models are not loaded yet.
Option 3.5 You could probably trick autoloader to do the heavy lifting (though, this might seem more like a hack):
class Guest::DocumentsController < ActionController::Base
# trigger autoload
Guest::DocumentPresenter
def show
# This should refer Guest::DocumentPresenter
DocumentPresenter.new(find_document)
end
def show
# As will this
DocumentPresenter.new(find_document)
end
end
Still the cleanest would be to rename the base class.
I think in 3 solutions if you want to mantein the name, one is your second solution.
1) explicitly require appropriate presenter files in controller files
2) Execute the full environment class path, like:
class Guest::DocumentsController < ActionController::Base
def show
Guest::DocumentPresenter.new(find_document)
end
end
3) Create a file on initialize directory and execute require manually (the worst options :S)

Resources