Split Grape API (non-Rails) into different files - ruby

I am writing an API in Grape, but it stands alone, with no Rails or Sinatra or anything. I'd like to split the app.rb file into separate files. I have looked at How to split things up in a grape api app?, but that is with Rails.
I'm not sure how to make this work with modules or classes — I did try subclassing the different files into my big GrapeApp, but that was ugly and I'm not even sure it worked properly. What's the best way to do this?
I currently have versions split by folders (v1, v2, etc) but that is all.

You don't need to subclass from your main app, you can just mount separate Grape::API sub-classes inside the main one. And of course you can define those classes in separate files, and use require to load in all the routes, entities and helpers that you app might need. I have found it useful to create one mini-app per "domain object", and load those in app.rb, which looks like this:
# I put the big list of requires in another file . .
require 'base_requires'
class MyApp < Grape::API
prefix 'api'
version 'v2'
format :json
# Helpers are modules which can have their own files of course
helpers APIAuthorisation
# Each of these routes deals with a particular sort of API object
group( :foo ) { mount APIRoutes::Foo }
group( :bar ) { mount APIRoutes::Bar }
end
I arrange files in folders, fairly arbitrarily:
# Each file here defines a subclass of Grape::API
/routes/foo.rb
# Each file here defines a subclass of Grape::Entity
/entities/foo.rb
# Files here marshal together functions from gems, the model and elsewhere for easy use
/helpers/authorise.rb
I would probably emulate Rails and have a /models/ folder or similar to hold ActiveRecord or DataMapper definitions, but as it happens that is provided for me in a different pattern in my current project.
Most of my routes look very basic, they just call a relevant helper method, then present an entity based on it. E.g. /routes/foo.rb might look like this:
module APIRoutes
class Foo < Grape::API
helpers APIFooHelpers
get :all do
present get_all_users_foos, :with => APIEntity::Foo
end
group "id/:id" do
before do
#foo = Model::Foo.first( :id => params[:id] )
error_if_cannot_access! #foo
end
get do
present #foo, :with => APIEntity::Foo, :type => :full
end
put do
update_foo( #foo, params )
present #foo, :with => APIEntity::Foo, :type => :full
end
delete do
delete_foo #foo
true
end
end # group "id/:id"
end # class Foo
end # module APIRoutes

Related

How to request separate folder view path based on controller name in Sinatra?

Here's the contents of my app/controllers/application_controller.rb:
require 'sinatra/base'
require 'slim'
require 'colorize'
class ApplicationController < Sinatra::Base
# Global helpers
helpers ApplicationHelper
# Set folders for template to
set :root, File.expand_path(File.join(File.dirname(__FILE__), '../'))
puts root.green
set :sessions,
:httponly => true,
:secure => production?,
:expire_after => 31557600, # 1 year
:secret => ENV['SESSION_SECRET'] || 'keyboardcat',
:views => File.expand_path(File.expand_path('../../views/', __FILE__)),
:layout_engine => :slim
enable :method_override
# No logging in testing
configure :production, :development do
enable :logging
end
# Global not found??
not_found do
title 'Not Found!'
slim :not_found
end
end
As you can see I'm setting the views directory as:
File.expand_path(File.expand_path('../../views/', __FILE__))
which works out to be /Users/vladdy/Desktop/sinatra/app/views
In configure.ru, I then map('/') { RootController }, and in said controller I render a view with slim :whatever
Problem is, all the views from all the controllers are all in the same spot! How do I add a folder structure to Sinatra views?
If I understand your question correctly, you want to override #find_template.
I stick this function in a helper called view_directory_helper.rb.
helpers do
def find_template(views, name, engine, &block)
views.each { |v| super(v, name, engine, &block) }
end
end
and when setting your view directory, pass in an array instead, like so:
set :views, ['views/layouts', 'views/pages', 'views/partials']
Which would let you have a folder structure like
app
-views
-layouts
-pages
-partials
-controllers
I was faced with same task. I have little experience of programming in Ruby, but for a long time been working with PHP. I think it would be easier to do on it, where you can easily get the child from the parent class. There are some difficulties. As I understand, the language provides callback functions like self.innereted for solving of this problem. But it did not help, because I was not able to determine the particular router in a given time. Maybe the environment variables can help with this. But I was able to find a workaround way to solve this problem, by parsing call stack for geting caller class and wrapping output function. I do not think this is the most elegant way to solve the problem. But I was able to realize it.
class Base < Sinatra::Application
configure do
set :views, 'app/views/'
set :root, File.expand_path('../../../', __FILE__)
end
def display(template, *args)
erb File.join(current_dir, template.to_s).to_sym, *args
end
def current_dir
caller_class.downcase!.split('::').last
end
private
def caller_class(depth = 1)
/<class:([\w]*)>/.match(parse_caller(caller(depth + 1)[1]))[1]
end
def parse_caller(at)
Regexp.last_match[3] if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
end
end
The last function is taken from here. It can be used as well as default erb function:
class Posts < Base
get '/posts' do
display :index , locals: { variables: {} }
end
end
I hope it will be useful to someone.

Rails routing: Giving default values for path helpers

Is there some way to provide a default value to the url/path helpers?
I have an optional scope wrapping around all of my routes:
#config/routes.rb
Foo::Application.routes.draw do
scope "(:current_brand)", :constraints => { :current_brand => /(foo)|(bar)/ } do
# ... all other routes go here
end
end
I want users to be able to access the site using these URLs:
/foo/some-place
/bar/some-place
/some-place
For convenience, I'm setting up a #current_brand in my ApplicationController:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_brand
def set_brand
if params.has_key?(:current_brand)
#current_brand = Brand.find_by_slug(params[:current_brand])
else
#current_brand = Brand.find_by_slug('blah')
end
end
end
So far so good, but now I must modify all *_path and *_url calls to include the :current_brand parameter, even though it is optional. This is really ugly, IMO.
Is there some way I can make the path helpers automagically pick up on #current_brand?
Or perhaps a better way to define the scope in routes.rb?
I think you will want to do something like this:
class ApplicationController < ActionController::Base
def url_options
{ :current_brand => #current_brand }.merge(super)
end
end
This method is called automatically every time url is constructed and it's result is merged into the parameters.
For more info on this, look at: default_url_options and rails 3
In addition to CMW's answer, to get it to work with rspec, I added this hack in spec/support/default_url_options.rb
ActionDispatch::Routing::RouteSet.class_eval do
undef_method :default_url_options
def default_url_options(options={})
{ :current_brand => default_brand }
end
end

How to have a gem controller handle multiple arbitrary models?

I have four models that I allow commenting on by four separate comment controllers. Those four comment controllers do essentially the same thing and vary only slightly.
In an attempt to remove duplication of the four commenting controllers which are essentially all the same, I've created a Rails Engine as a gem to arbitrarily handle commenting on any arbitrary model that I specify in the routes.rb.
So in my routes.rb file I can now use:
comments_on :articles, :by => :users
with comments_on implemented as follows in my gem:
def comments_on(*resources)
options = resources.extract_options!
[snip of some validation code]
topic_model = resources.first.to_s
user_model = options[:by].to_s
# Insert a nested route
Rails.application.routes.draw do
resources topic_model do
resources "comments"
end
end
end
The routes show up in 'rake routes' and requests correctly get routed to my gem's 'CommentsController' but that's where my gem's functionality ends.
What is the best way detect the context in my gem CommentsController so I can process requests specific to how comments_on was called?
More specifically, how would I implement an index action like the following, having it context aware?
def index
#article = Article.find(params[:article_id])
#comments = ArticleComment.find(:all, :conditions => { :article_id => #article.id })
end
Thanks for the help!
You could specify the topic as an extra parameter in your routes:
Rails.application.routes.draw do
resources topic_model do
resources "comments", :topic_model => topic_model.to_s
end
end
Then your controller could be written like this:
def index
#topic = topic
#comments = topic.comments
end
protected
def topic
m = params[:topic_model]
Kernel.const_get(m).find(params["#{m.underscore}_id"])
end
You could move a lot of the logic out of the controller and into the model as well. topic.comments could be a named scope that all of these models should implement.
I've done similar patterns in the past and there's usually an edge-case that breaks this idea down and you end up doing more 'meta' programming than is wise.
I'd recommend making a base controller, then making simplistic controllers that inherit from that, or try to split these common behaviors into modules.

How do I include Responder in ActionController::Metal in Rails 3?

I'm working a Rails 3 controller that has a very specific, limited purpose, and all I need is for it to respond_to :json.
This screencast says my controller can inherit from ActionController::Metal, and then just include the functionality I need to make things faster:
http://rubyonrails.org/screencasts/rails3/action-controller
When my controller looks like this:
class FoldersController < ActionController::Metal
respond_to :json
def all
respond_with Folder.all
end
end
I get the error:
undefined method `respond_to' for FoldersController:Class
I've tried including Responder, ActionController::Responder, ActionController::Metal::Responder, but none of them work. What do I include to get this responder functionality?
You need to include more classes, not only Responder. Here is my ApplicationController, but not all includes you may need:
class Api::ApplicationController < ActionController::Metal
include ActionController::Helpers
include ActionController::UrlFor
include ActionController::Redirecting
include ActionController::Rendering # enables rendering
include ActionController::ConditionalGet # required for respond_to and respond_with
include ActionController::MimeResponds # enables serving different content types like :xml or :json
include ActionController::Cookies # enables cookies
include AbstractController::Callbacks # callbacks for your authentication logic
include ActiveSupport::Rescuable # enables resque_from
include Rails.application.routes.url_helpers
end
ActionController::MimeResponds looks to be the path.

How can I test helpers blocks in Sinatra, using Rspec?

I'm writing a sinatra app and testing it with rspec and rack/test (as described on sinatrarb.com).
It's been great so far, until I moved some rather procedural code from my domain objects to
sinatra helpers.
Since then, I've been trying to figure out how to test these in isolation ?
I test my sinatra helpers in isolation by putting the helper methods within its own module.
Since my sinatra application is a little bit bigger than the usual hello world example, I need to split it up into smaller parts. A module for the common helpers suits my use case well.
If you write a quick demo, and you define your helper methods within the helpers { ... } block, I don't think testing it is absolutely necessary. Any sinatra app in production, may require more modularity anyways.
# in helpers.rb
module Helpers
def safe_json(string)
string.to_s.gsub(/[&><']/) { |special| {'&' => '\u0026', '>' => '\u003E', '<' => '\u003C', "'" => '\u0027'}[special] }
end
end
# in app.rb
helpers do
include Helpers
end
# in spec/helpers_spec.rb
class TestHelper
include Helpers
end
describe 'Sinatra helpers' do
let(:helpers) { TestHelper.new }
it "should escape json to inject it as a html attribute"
helpers.safe_json("&><'").should eql('\u0026\u003E\u003C\u0027')
end
end
Actually you don't need to do:
helpers do
include FooBar
end
Since you can just call
helpers FooBar
The helpers method takes a list of modules to mix-in and an optional block which is class-eval'd in: https://github.com/sinatra/sinatra/blob/75d74a413a36ca2b29beb3723826f48b8f227ea4/lib/sinatra/base.rb#L920-L923
maybe this can help you some way http://japhr.blogspot.com/2009/03/sinatra-innards-deletgator.html
I've also tried this (which needs to be cleaned up a bit to be reusable) to isolate each helper in its own environment to be tested:
class SinatraSim
def initialize
...set up object here...
end
end
def helpers(&block)
SinatraSim.class_eval(&block)
end
require 'my/helper/definition' # defines my_helper
describe SinatraSim do
subject { SinatraSim.new(setup) }
it "should do something"
subject.expects(:erb).with(:a_template_to_render) # mocha mocking
subject.my_helper(something).should == "something else"
end
end

Resources