Rails - How to organize constants - ruby-on-rails

I am having some constants that I want to define globally in my applications. Also, these constants don't belong to any model. What's the best practice to organize those constants.
Any advice is highly appreciated

simple you can define Module called as example Constants and define it inside the module
module Constants
CONST1="value"
CONST2="value2"
end
it's possible also to group them like
module Constants
module Group1
CONST1="value"
CONST2="value2"
end
module Group2
# some relevant constants
end
end

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

How to require module from gem to use its classes without namespace?

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

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.

Models with reserved keywords

I want to create a model called 'File', but it is a reserved model name is rails. I can't think of anything else sane to call the model, so I was wondering if there is a standard way of dealing with this issue, for example adding a prefix or suffix (_File, FileItem, etc)?
This problem is addressed with modules:
Modules are a way of grouping together methods, classes, and
constants. Modules give you two major benefits:
Modules provide a namespace and prevent name clashes.
Modules implement the mixin facility.
[...]
Modules define a namespace, a sandbox in which your methods and
constants can play without having to worry about being stepped on by
other methods and constants.
In your case:
module MyRailsApp
class File
...
end
end
whereby your File class is used as MyRailsApp::File. This is the typical solution in Ruby, in Ruby on Rails this might be handled differently, please see the following references for an in depth discussion:
Handling namespace models (classes) in namespace
ActiveRecord: Can haz namespaces?
Namespaced models and controllers
Namespaced models
A simple alternative to namespaced models

Resources