How can I use mixins or modules in my controllers in Rails 3? - ruby-on-rails-3

I have some behavior in my controller that I pulled out into a module in order to test better and re-use it in a few places. Two questions about this:
Where is a good place to put my modules? They need to run in order to be available to the controllers, so I was thinking the config/initializers/ directory. That seems a little suspect to me though. lib/?
How do I ensure the code gets run so the modules are available to include in my controllers?
Thank you kindly sirs.

lib/ is an excellent place for modules; much better than config/initializers/--at least in my opinion. If it's several modules, or one large one, you can also consider making it a plugin and placing it in vendor/plugins.
If you put it in lib/, you'll need to manually require the file. Rails, by default, does not autoload files in the lib/ directory. You can place the require in one of your config files.
I usually put my additional autoloads in config/application.rb. Something like this should do the trick (assuming that your .rb file is in a directory called lib/my_module):
config.autoload_paths += Dir["#{Rails.root}/lib/my_module"]
You have to make sure that your module is an actual module and not a class. Then, you can simply include it:
# lib/my_module/foobar.rb
module Foobar
def foobar
"Hello world!"
end
end
# app/models/my_model.rb
class MyModel < ActiveRecord::Base
include Foobar
end
# rails console
>> obj = MyModel.first
=> #<MyModel id: 1, ...>
>> obj.id
=> 1
>> obj.foobar
=> "Hello world!"

1) I like to put:
my class extentions under app/extentions
my modules under /app/mixins
my services under /app/services
2) You can configure your application to load all of these in config/application.rb:
class extentions should be required right way
and the mixins and services can be added to your autoload path
class Application < Rails::Application
# require class extentions right now
Dir[Rails.root.join('app', 'extentions', "*.rb")].each {|l| require l }
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += Dir[Rails.root.join('app', 'mixins', '{**}')]
config.autoload_paths += Dir[Rails.root.join('app', 'services', '{**}')]
(I'm using rails 3)

Try putting controller specific modules in app/controllers. No require required.

Related

How do I include types from a gem with sigs?

Let's say I have a shared gem called thing that gets used in multiple other projects, like example-project. I want to use sorbet in my thing gem to take advantage of the type checking. How do I get my consuming projects to check against the types in my gem?
For example, the code might look like this
thing gem
# typed: strict
require 'sorbet-runtime'
class Thing
extend T::Sig
sig { params(phrase: String).returns(String) }
def say(phrase)
"Thing said: '#{phrase}'"
end
end
example-project
# typed: strict
require 'thing'
Thing.new.say(1) # Should cause a type error
What happens
After running srb rbi update, I get an sorbet/rbi/gems/thing.rbi
class Thing
def say(*args, &blk); end
extend T::Private::Methods::MethodHooks
extend T::Private::Methods::SingletonMethodHooks
extend T::Sig
end
This doesn't have the sigs from the gem that I'm expecting, and srb tc doesn't raise the error I'm expecting.
Other things I've tried
Including rbi/thing.rbi in the gem
This works, but I'd rather write my sigs inline in the gem code than maintain separate rbi files. Is there any tooling that could take the .rb files with sigs and output the .rbi files (and maybe strip the sigs from the .rb files so them gem can be distributed without sorbet). I'm thinking of a workflow similar to typescript's tsc --declaration, which goes from .ts => .d.ts + .js.
https://github.com/AaronC81/brain_freeze
is a new project that aims to make it possible to generate an RBI file from RB files with inline declarations. It's still early, but might be worth trying out

Basic usage of modules in puppet with hiera

I want to use puppet to manage some servers. Even after reading dozens of documentation pages, it is not clear to me how to use modules and how to use them with hiera. As first experiment I wanted a user "admin" to be created on one node and found this module -> https://github.com/camptocamp/puppet-accounts
My /etc/puppet/hiera.yaml looks as simple as this
---
:backends:
- yaml
:hierarchy:
- node/%{::fqdn}
- common
:yaml:
:datadir: /etc/puppet/hieradata
My /etc/puppet/hieradata/node/node1.example.com.yaml contains this
---
accounts::users:
admin:
uid: 1010
comment: admin
accounts::ssh_keys:
admin:
comment: ad
type: ssh-rsa
public: AAAAAAAAAAAAAA
This worked after I put this in my /etc/puppet/manifests/site.pp
hiera_include('classes')
class
{
'accounts':
ssh_keys => hiera_hash('accounts::ssh_keys', {}),
users => hiera_hash('accounts::users', {}),
usergroups => hiera_hash('accounts::usergroups', {}),
}
accounts::account
{
'admin':
}
Is this good practice? To me it feels wrong to put that stuff into site.pp since it gets messed up when I later use more modules. But where else to put it? I also don't understand how this separates data from logic, since I have data in both, node1.example.com.yaml and site.pp (admin). Some help would be great..
To understand what hiera is, you should think simply that Hiera is a DATABASE for puppet, a database of Variables/values and nothing more.
For a beginner I would suggest to focus on other parts of the system, like how to create modules! and how to manage your needs (without complexity) and then slowly build the "smart" recipes or the reusable ones...
Your puppet will first sick for a file called sites.pp (usually is on your main $confdir (puppet.conf variable. I am not going to mention environments it is for later.)
e path is /etc/puppet inside that directory, you have a directory manifests. There is the place for your sites.pp
usually a sites.pp structure is:
node default {
include *module*
include *module2*
}
node /server\.fqdn\.local/ {
include *module2*
include *module3*
}
this means that you have a default Node (if the node name doesn't fit any other node, will use the default, otherwise it will use the regex matching of the node FQDN in this case server.fqdn.local.
The modules (module, module2 and module3) are stored inside the $modulespath set on your puppet.conf. In our case i will use the: /etc/puppet/modules
the tree will look like:
/etc/puppet/modules/
/etc/puppet/modules/module/
/etc/puppet/modules/module/manifests/
/etc/puppet/modules/module/manifests/init.pp
/etc/puppet/modules/module2/
/etc/puppet/modules/module2/manifests/
/etc/puppet/modules/module2/manifests/init.pp
/etc/puppet/modules/module3/
/etc/puppet/modules/module3/manifests/
/etc/puppet/modules/module3/manifests/init.pp
About
classes: https://docs.puppetlabs.com/puppet/latest/reference/lang_classes.html
generally what i explained but from puppet labs: https://docs.puppetlabs.com/puppet/latest/reference/dirs_manifest.html
Please note that the example from the README
class { 'accounts':
ssh_keys => hiera_hash('accounts::ssh_keys', {}),
users => hiera_hash('accounts::users', {}),
usergroups => hiera_hash('accounts::usergroups', {}),
}
is catering to users of Puppet versions before 3.x which had no automatic parameter lookup. With a recent version, you should just use this manifest:
include accounts
Since the Hiera keys have appropriate names, Puppet will look them up implicitly.
This whole thing still makes no sense to me. Since I have to put
accounts::account
{
'admin':
}
in a manifest file to create that user, what for is hiera useful in this case? It doesn't separate data from logic. I have data in both, the .yaml file (ssh keys, other account data) and in a manifest file (the snippet above). By using hiera I expect to be able to create that user inside /etc/puppet/hieradata/node/node1.example.com.yaml but this is not the case. What is the right way to do this? What for is the example hiera file of this module useful? Wouldn't it be easier create an account the old style way in site.pp?

How to autoload files in folder under rails app's root

I am trying to have files under myapplication/somefolder. Google and Stackoverflow say I should add this:
config.autoload_paths += %W(#{config.root}/somefolder)
in my config/application.rb, so I did.
But the files don't get loaded.
I tried namig somefolder/myclass.rb both class Myclass and class Somefolder::Myclass but still no luck.
I can see that the dir was found in Rails.application.config.autoload_paths in console does indeed include my /path/to/myapplication/somefolder directory, so that should be okay.
All the other questions around this topic use theapp/app/somefolder or theapp/lib/somefolder but not theapp/somefolder so maybe thats where it gets rotten.
So I tried referencing the class with ::Somefolder::MyClass but not even that helped.
I am using Rails 3.2.1
Just ran into this myself today and decided to dive deep.
The reason you don't see in ActiveSupport::Dependencies.autoload_paths the paths you're adding to config.autoload_paths in config/application.rb is that they aren't copied over until the application is initialized. See rails/engine.rb in the railties gem:
module Rails
class Engine < Railtie
…
# Set the paths from which Rails will automatically load source files,
# and the load_once paths.
#
# This needs to be an initializer, since it needs to run once
# per engine and get the engine as a block parameter
initializer :set_autoload_paths, :before => :bootstrap_hook do |app|
ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths)
# Freeze so future modifications will fail rather than do nothing mysteriously
config.autoload_paths.freeze
config.eager_load_paths.freeze
config.autoload_once_paths.freeze
end
…
def _all_autoload_paths
#_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq
end
…
end
end
Were you by any chance trying to invoke MyClass from within config/application.rb or, even earlier, from a script or module that requires config/application.rb? If so, you'll have to explicitly require the file defining MyClass, e.g.:
require File.expand_path('../../somefolder/my_class', __FILE__)
# now use MyClass
The workaround is to go to ActiveSupport::Dependencies.autoload_paths directly.
ActiveSupport::Dependencies.autoload_paths << "#{config.root}/somefolder"
But I am still looking for the reason why config.autoload_paths didn't work so if you post an aswer to that, I'll accept it!
You should name somefolder/my_class.rb in order to autoload MyClass. You also should keep that config.autoload_paths += %W(#{config.root}/somefolder) line in your config/application.rb.

Extend a module in Rails 3

I want to define a function available_translations which lists the translations I have made for my application into the I18n module.
I tried putting the following into the file lib/i18n.rb, but it doesn't work when I try to use it from the rails console:
module I18n
# Return the translations available for this application.
def self.available_translations
languages = []
Dir.glob(Rails.root.to_s + '/config/locales/*.yml') do |filename|
if md = filename.match #^.+/(\w+).yml$#
languages << md[1]
end
end
languages
end
end
Console:
ruby-1.9.2-p290 :003 > require Rails.root.to_s + '/lib/i18n.rb'
=> false
ruby-1.9.2-p290 :004 > I18n.available_translations
NoMethodError: undefined method `available_translations' for I18n:Module
...
Besides solving my concrete problem, I would be very pleased to learn how this whole module thing in Ruby on Rails works because it still confuses me, so I would appreciate links to the docs or source code very much.
Either of these will solve your problem:
move the code to config/initializers/i18n.rb, or
require your file from config/application.rb, or
name your class otherwise (to trigger autoload)
The code in lib/i18n.rb wil not be loaded by autoload since I18n name will be already loaded, so either you load it yourself or change the class name (and file name) so the new name will trigger autoload behavior.
BTW, the I18n.available_locales() method is presented in rails.

Seeking finer-grained logging options in Rails. How can I log entire requests and/or individual filters to alternative log files?

This is a two part question, but may have the same answer.
Part One:
In our app, one particular controller get hit a lot -- so much so that we'd like to it be logged in a file separate from all other requests. Setting the FoosController.logger is not what I'm looking for, because the request exercises some lib files and active record object that have their logger object, and rails will log some info before handing control to the controller in question.
Part Two:
We have a global before filter included in our root application_controller.rb that is run before most actions of most controllers. This before_filter is very wordy in the logs, and is a candidate for having all its logging info sent to a separate file. This before filter also calls out to libs and ActiveRecord code with their own refererences to the logger.
One possible solution is to run the single controller as its own standalone application. I haven't tried it yet, because it's pretty tied into the internals of app. This approach also would not help with the before_filter.
Are there any good solutions for more fine-grained logging in rails apps?
Thanks!
For part I
I recommend creating your own logger class (possibly inheriting from the ruby logger (1), have the logger filter out the request url and based on that log to a specific file.
For part II
the easiest solution would be to just use a seperate logger for these methods. Instead of logger.debug "your message" you just call method_logger.debug "your message".
Creating your own logger is simple, just use the ruby logger class (1). The following code creates a logger that logs to a my_method.log file in the logs dir of your rails application.
method_logger = Logger.new(Rails.root.join('logs','my_method.log')
You can then write to your log with the following command, which should look familiar to you as rails uses the Ruby logger as well.
method_logger.debug "your debug message here"
(1) http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html
Here's the code for a custom logger that you can use to solve Part I. I adapted it for my own use from another Stack Overflow question, even including the answerer's inline comments. The main difference is that you can specify a different log path for development and production:
# app/concerns/event_notifications_logger.rb
class EventNotificationsLogger < Rails::Rack::Logger
def initialize(app, opts = {})
#default_logger = Rails.logger
#notifications_logger = Logger.new(notifications_log_path)
#notifications_logger.formatter = LogFormat.new
#notifications_logger.level = #default_logger.level
#app = app
#opts = opts
end
def call(env)
logger = if env['PATH_INFO'] == '/event_notifications/deliver'
#notifications_logger
else
#default_logger
end
# What?! Why are these all separate?
ActiveRecord::Base.logger = logger
ActionController::Base.logger = logger
Rails.logger = logger
# The Rails::Rack::Logger class is responsible for logging the
# 'starting GET blah blah' log line. We need to call super here (as opposed
# to #app.call) to make sure that line gets output. However, the
# ActiveSupport::LogSubscriber class (which Rails::Rack::Logger inherits
# from) caches the logger, so we have to override that too
#logger = logger
super
end
private
def notifications_log_path
Rails.env.production? ? '/var/log/event-notifications.log' : Rails.root.join('log/event-notifications.log')
end
end
Then include it in your application configuration:
require File.expand_path('../boot', __FILE__)
require 'rails/all'
require File.expand_path('../../app/concerns/event_notifications_logger', __FILE__)
module YourApp
class Application < Rails::Application
config.middleware.swap Rails::Rack::Logger, EventNotificationsLogger
# ...
end
end
Rails 3.2 provides filter options for the logs, called "Tagged Logging".
See the announcement or the new Rails 3.2 guide on ActiveSupport.