How do I make functions in a gem available to Sinatra views? - ruby-on-rails

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.

Related

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 to extend World in the context of cucumber-rails?

I am building an API with Rails, using the rails-api gem. I want to use cucumber-rails and the gem 'Airborne' to test it.
Airborne comes with some nice helper methods for testing API responses, which I want to have access to in my step definitions. I have done this kind of thing before in Sinatra, which was relatively straightforward to configure in the /features/env.rb file.
It seems, however, that with rails-cucumber the creation of the 'World' happens behind the scenes somewhere and I don't know how to extend it to use the Airborne module after it's been created.
I have tried the following:
Airborne.configure do |config|
config.rack_app = Rails.application
end
Cucumber::Rails::World.extend(Airborne)
When(/^I make a request for information about an event$/) do
get "/events/1"
end
Then(/^I receive the information as a JSON$/) do
expect_json {}
end
I am still getting a NoMethodError on #expect_json, which is an Airborne method.
So my question is: how can I extend the instance of World in the context of cucumber-rails?
Don't panic, the World has been saved. The solution is to wrap Airborne and whatever else in a module:
module MyHelpers
include Airborne
include Capybara::DSL
end
Then pass that:
World(MyHelpers)

How to include a specific Rails method (or file) in a non-Rails Ruby project?

I would like to use the distance_of_time_in_words method in the date_helper.rb Rails file (see on Github) in an non-Rails Ruby project.
How can I include it? It requires other files, so how to include them?
I don't want to include all of Rails because that would slow down the development process.
Ruby 1.9.3
This method, distance_of_time_in_words is in actionpack/lib/action_view/helpers/date_helper.rb. So you should require 'action_view' and action_view/helpers to load this method. And the method is defined in module ActionView::Helpers::DateHelper, you can include it in your class. The method is an instance method.
require 'action_view'
require 'action_view/helpers'
class Klass
include ActionView::Helpers::DateHelper
end
c = Klass.new
c.distance_of_time_in_words( ...... )
If this is the only thing you want from it, then I'd just go take the source code and hack it to remove the dependencies (which appears to just be some I18n translations. To support the hack, you can probably translate this test suite.
Why would I do this instead of using the gem? Because it's just such an enormous dependency. It's so enormous that you actually notice it loading all that code. I'd rather rip out the method and hack it to work than depend on all of that (again, assuming this is the only thing you want from the lib).

Some question about classes in plugin

I am using Ruby on Rails 3 and and I am trying to implement a new plugin. In order to learn, I am viewing inside and I am studying some popular plugins.
What I choosed is WillPaginate and in a its file there is something like this:
module WillPaginate
class << self
...
end
end
if defined? Rails
WillPaginate.enable_activerecord if defined? ActiveRecord
WillPaginate.enable_actionpack if defined? ActionController
end
I would like to know
Why the if defined? Rails statement is outside the module statement? When will be run istructions inside that?
What means and how can\should I use class << self?
module WillPaginate defines Ruby name scope and groups these methods so they can be later included with one call into some class. The if defined? Rails is outside the module because the code inside that if might include the whole module into some ActiveRecord class. And the if is executed exactly at the time when will_paginate.rb file is loaded.
All methods in that block are class methods. So later it is possible to make calls like YourModelClass.foo.
The if defined? Rails block is evaluated at load time, ie during require 'will_paginate'. That allows will_paginate to be used with or without Rails.
The class << self section is a way to define a group of methods on the WillPaginate module without having to define them all as def self.method_name. Either way works (except for a few edge cases I can't remember now), so it's mostly just a style choice.

Resources