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.
Related
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.
I'd like to create a simple experimental MVC framework using Sinatra.
I'd like to define resources by name "pages" for example should resolve to:
/pages (index)
/pages/new
/pages/:id/show (show)
as WELL as map to app/controllers/PagesController.rb with corresponding get('/') to be responsible for the index, post('/pages/create') be responsible for creation, etc.
Trouble is even after reading the official documentation I'm terribly confused. I imagine I need to use non-classic Sinatra model for this, but could anyone point me in the right direction?
Thank you
If you want what I think you're wanting, I do this all the time. Initially for this scheme I used the travis-api source as a reference, but essentially what you want to do is extend Sinatra::Base in a "controller" class and then mount up your individual Sinatra "controllers" in rack, something like this:
module Endpoint
def self.included(base)
base.class_eval do
set(:prefix) { "/" << name[/[^:]+$/].downcase }
end
end
end
class Users < Sinatra::Base
include Endpoint
get '/' do
#logic here
end
get '/:id' do
#logic here
end
post '/' do
#logic here
end
patch '/:id' do
#logic here
end
end
class Posts < Sinatra::Base
include Endpoint
post '/' do
#logic here
end
end
and then something like this:
class App
require "lib/endpoints/users"
require "lib/endpoints/posts"
attr_reader :app
def initialize
#app = Rack::Builder.app do
[Users, Posts].each do |e|
map(e.prefix) { run(e.new) }
end
end
end
def call(env)
app.call(env)
end
end
You can adjust this to whatever you need, but the idea is the same, you separate your app into composable Sinatra applications that each have a prefix that they are mounted under using Rack. This particular example will give you routes for:
get '/users'
get '/users/:id'
post '/users'
patch '/users/:id'
get '/posts'
I'll give you a very simple example here:
Create a file controller.rb
get '/pages' do
#pages = Pages.all
erb :pages
end
Next create a views directory in the same folder as teh controller, and create a file named pages.html.erb
This is the corresponding view to your previously created controller action.
Here, you can type something like:
<% #pages.each do |p| %>
<%= p.title %>
<% end %>
Restart your server, visit localhost:PORT/pages and you will see a list of all your page titles.
You can check out this link for a simple sinatra tutorial - http://code.tutsplus.com/tutorials/singing-with-sinatra--net-18965
You can make this as complicated or as simple as you need. For example:
Rails makes a lot of magic happen under the hood, whereas Sinatra is more flexible at the cost of requiring you to implement some of this stuff yourself.
controller_map = {
'pages' => PagesController
}
post '/:controller/new' do
c = params[:controller]
module = controller_map[c]
module.create_new()
...
end
get '/:controller/:id/show' do
c = params[:controller]
id = params[:id]
module = controller_map[c]
module.get(id)
...
end
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
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
I need to render a Sinatra erb template inside a class in my controller. I'm having issues calling this though. I've looked in the Sinatra rdocs and have come up with this:
Sinatra::Templates.erb :template_to_render
When I do this, I get the following error:
undefined method `erb' for Sinatra::Templates:Module
Is there a way to call this from another class?
To imitate rendering behavior of Sinatra controller in some other class (not controller) you can create module like this:
module ErbRender
include Sinatra::Templates
include Sinatra::Helpers
include Sinatra::ContentFor
def settings
#settings ||= begin
settings = Sinatra::Application.settings
settings.root = "#{ROOT}/app"
settings
end
end
def template_cache
#template_cache ||= Tilt::Cache.new
end
end
Here you may need to tune settings.root
Usage example:
class ArticleIndexingPostBody
include ErbRender
def get_body
erb :'amp/articles/show', layout: :'amp/layout'
end
end
This will properly render templates with layouts including content_for
why you don't require 'erb' and after use only erb
## You'll need to require erb in your app
require 'erb'
get '/' do
erb :index
end
You could have your class return the template name and render it in the main app.
Of course that's not exactly an answer (I don't have enough rep to add a comment with this account) and you're probably doing just that by now anyway...