Coder Social home page Coder Social logo

scorched's Introduction

Scorched is a generic, unopinionated, DRY, light-weight web framework for Ruby. It provides a generic yet powerful set of constructs for processing HTTP requests, with which websites and applications of almost any scale can be built.

If you've used a light-weight DSL-based Ruby web framework before, such as Sinatra, Scorched should look quite familiar. Scorched is a true evolutionary enhancement of Sinatra, with more power, focus, and less clutter.

Getting Started

Install the canister...

$ gem install scorched

Open the valve...

# hello_world.ru
require 'scorched'
class App < Scorched::Controller
  get '/' do
    'hello world'
  end
end
run App

And light the flame...

$ rackup hello_world.ru

A Note on Requirements

Scorched requires Ruby 2.0 or above. If you've got Ruby 2.0.0-p195 and newer, you're good. Otherwise, you need to ensure that your version of Ruby 2.0 includes changeset 39919 in order to avoid suffering from random segmentation faults.

The Errors of Our Past (and Present!)

One of the mistakes made by a lot of other Ruby frameworks is to not leverage the power of the class. Consequently, this makes for some awkwardness. Helpers for example, are a classic reinvention of what classes and modules are already made to solve. Scorched implements Controllers as classes, which in addition to having their own DSL, allow you to define and call whatever you need as standard instance methods. The decision to allow developers to implement helpers and other common functionality as standard instance methods not only makes Controllers somewhat more predictable and familiar, but also allows for such helpers to be inheritable via plain old class inheritance.

Another design oversight of other frameworks is the lack of consideration for the hierarchical nature of websites and the fact that it's often desireable for sub-directories to inherit attributes of their parents. Scorched supports sub-controllers to any arbitrary depth, with each controller's configuration, filters, route conditions, etc. applied along the way. This can help in many areas of web development, including security, restful interfaces, and interchangeable content types.

Design Philosophy

Scorched has a relatively simple design philosophy. The main objective is to keep Scorched lean and generic. Scorched refrains from expressing any opinion about how you should design and structure your application. The general idea is to give developers the constructs to quickly put together small, medium and even large websites and applications.

There is little need for a framework to be opinionated if the opinions of the developer can be quickly and easily built into it on a per-application basis. To do this effectively, developers will really need to understand Scorched, and the best way to facilitate that is to lower the learning curve, by keeping the core design logical, predictable and concise.

Magicians Not Welcome

Scorched aims to be raw and transparent. Magic has no place. A thoughtful and simple design means there's no requirement for magic. Because of that, most developers should be able to master Scorched in an evening.

Part of what keeps Scorched lightweight is that unlike other lightweight web frameworks that attempt to hide Rack in the background, Scorched makes no such attempt, very rarely providing functionality that overlaps with what's already provided by Rack. In fact, familiarity with Rack is somewhat of a pre-requisite to mastering Scorched.

First Impressions

class MyApp < Scorched::Controller

  # From the most simple route possible...
  get '/' do
    "Hello World"
  end

  # To something that gets the muscle's flexing
  route '/articles/:title/::opts', 2, method: ['GET', 'POST'], content_type: :json do
    # Do what you want in here. Note, the second argument is the optional route priority.
  end

  # Anonymous controllers allow for convenient route grouping to which filters and conditions can be applied
  controller conditions: {media_type: 'application/json'} do
    get '/articles/*' do |page|
      {title: 'Scorched Rocks', body: '...', created_at: '27/08/2012', created_by: 'Bob'}
    end

    after do
      response.body = response.body.to_json
    end
  end

  # The things you get for free by using Classes for Controllers (...that's directed at you Padrino)
  def my_little_helper
    # Do some crazy awesome stuff that no route can resist using.
  end

  # You can always avoid the routing helpers and add mappings manually. Anything that responds to #call is a valid
  # target, with the only minor exception being that proc's are instance_exec'd, not call'd.
  self << {pattern: '/admin', priority: 10, target: My3rdPartyAdminApp}
  self << {pattern: '**', conditions: {maintenance_mode: true}, target: proc { |env|
    @request.body << 'Maintenance underway, please be patient.'
  }}
end

This API shouldn't look too foreign to anyone familiar with frameworks like Sinatra, and the potential power at hand should be obvious. The route method demonstrates a few minor features of Scorched:

  • Multi-method routes - Because sometimes the difference between a GET and POST can be a single line of code. If no methods are provided, the route receives all HTTP methods.
  • Named Wildcards - Not an original idea, but you may note the named wildcard with the double colon. This maps to the '**' glob directive, which will span forward-slashes while matching. The single asterisk (or colon) behaves like the single asterisk glob directive, and will not match forward-slashes.
  • Route priorities - Routes (referred to as mappings internally) can be assigned priorities. A priority can be any arbitrary number by which the routes are ordered. The higher the number, the higher the priority.
  • Conditions - Conditions are merely procs defined on the controller which are inherited (and can be overriden) by child controllers. When a request comes in, mappings that match the requested URL, first have their conditions evaluated in the context of the controller instance, before control is handed off to the target associated with that mapping. It's a very simple implementation that comes with a lot of flexibility.

Comparisons with other frameworks

Refer to the comparisons directory in the repo to compare a simple example app written in frameworks similar to Scorched.

Links

scorched's People

Contributors

andreypopp avatar arturdryomov avatar crowdhailer avatar dummey avatar jf avatar mrded avatar readmecritic avatar wardrop avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scorched's Issues

Is this still actively maintained?

I'm writing a gem that has to use a mini web framework to load up a git webhook receiver, I've tried a few other frameworks, namely

  • Sinatra
    • Hangs on install so it can't be used in a docker container, which a prime user of my gem wants to do.
    • Is a bit more heavy than I want it to be
  • NYNY
    • Doesn't hang on install, but constantly is giving me errors about a mismatched superclass and I can't seem to find the problem. And frustrates me with its lacking features.

But I also want to know if I'm able use App.run! in a Thor command instead of a config.ru so that the server can be started in a thread and stopped with a 'quit' command (which I have implemented in previous versions of the gem)

And also I want to know if i can easily change the bind host and bind port of the server, as I had set up a class method on my sinatra version to set the host and port, and NYNY only allows port, and not as nicely.

Clone render_default hash during inheritance?

So I have just wandered into a bug that was caused by the ability for sub controllers to modify the contents of the render_defaults hash. This is a surmised config.ru

APP_ROOT = File.expand_path('../', __FILE__) unless defined?(APP_ROOT)

require 'scorched'

class App < Scorched::Controller
  render_defaults[:dir] = File.expand_path('app/views', APP_ROOT)
end

class OtherController < App
  render_defaults[:dir] << '/other'
end

class HomeController < App
  render_defaults[:dir] << '/home'
  get '/' do
    render :index
  end
end

App.controller '/', HomeController

run App

This will throw the following error, where both other and home have been added to the render path.

Errno::ENOENT at /
No such file or directory @ rb_sysopen - /My/Path/app/views/other/home/index.erb

I am happy to be told that the shovel operation with strings there is just bad practise. Thoughts?
But as a suggestion I would deep copy this and the config to children controllers

Provide configuration option for template encoding

There's only so much Tilt can do to help when it comes to intelligently choosing an encoding for reading templates. Scorched needs to provide a sane/common default, such as UTF-8 or some kind of auto-fallback, as well as the option to completely override the encoding of templates.

Refer to: rtomayko/tilt#213

406 on even the basic example?

This line in lib/scorched/controller.rb is causing the problem:

after(failed_condition: %i{charset encoding language media_type}) { response.status = 406 }

And the basic example on scorchedrb.com:

require 'scorched'
class App < Scorched::Controller
get '/' do
'hello world'
end
end

Query. What is the requirement to add middleware as a proc in controllers

I have a controller that looks like the following.

class App < Scorched::Controller
  middleware << proc do
    use Rack::Session::Cookie, secret: 'blah'
    use Rack::Csrf, :raise => true
  end
end

I was looking into adding code to be able to do the following.

class App < Scorched::Controller
    use Rack::Session::Cookie, secret: 'blah'
    use Rack::Csrf, :raise => true
end

However I was wondering if you had a reason to not do this before I tripped over a problem you had already thought about

`<class:App>': uninitialized constant App::ControllerA (NameError)

require 'scorched'

class App < Scorched::Controller
  get '/' do
   'root'
  end

  self << {pattern: '/a', priority: 10, target: ControllerA}
end


class ControllerA < Scorched::Controller
  get '/' do
    "sub a at App"
  end

  self << {pattern: '/b', priority: 10, target: ControllerB}


end


class ControllerB < Scorched::Controller
  get '/' do
    "sub b  at a"
  end

end

Is Scorched must be strict order by [ControllerB ,ControllerA, App] ?

Unable to get better errors to work with scorched

I have been trying to implement the 'better_errors' gem as it is the alternative to web console that should work on any rack application

At the moment when implemented I can get the backtrace of the error but not the code and console. If looking at the screenshot on the README for better errors I am just getting the left half of the screen. I think the problem is with the second step to load the code. the JS console on the error page I am getting has the following

Failed to load resource: the server responded with a status of 500 (Internal Server Error) http://localhost:9292/__better_errors/8052340bdb489f27/variables

I have tried getting this to work in an isolated application so can just copy the whole config.ru here

# setup as development enviroment unless otherwise specified
RACK_ENV = ENV['RACK_ENV'] ||= 'development' unless defined?(RACK_ENV)

# Set Application Root
APP_ROOT = File.expand_path('..', __FILE__) unless defined?(APP_ROOT)

# Load our dependencies
require 'rubygems' unless defined?(Gem)

# Sets up all of load paths that are searched when requiring code
require 'bundler/setup'

# requires all gems for the current runtime enviroment
Bundler.require(:default, RACK_ENV)

# require the lib directory
Dir[APP_ROOT + '/lib/**/*.rb'].each {|file| require file }

class App < Scorched::Controller
  middleware << proc do
    use Rack::Session::Cookie, secret: 'blah'
    use BetterErrors::Middleware

    BetterErrors.application_root = APP_ROOT
    use Rack::Csrf, :raise => true
  end

end

class HomeController < App
  get '/' do
    config[:show_exceptions]
  end

  get '/bad_route' do
    1/0
  end
end

App.controller '/', HomeController
run App

My lib directory is currently empty. I was wondering if csrf is the problem. but I have tried ommiting one or other middlewares as well as reordering.

Finally here is my gemfile

source 'https://rubygems.org'

ruby '2.2.0'

gem 'thin'
gem 'shotgun'
gem 'scorched'
gem 'rack_csrf', :require => 'rack/csrf'
gem 'awesome_print'

group :development do
  gem "better_errors"
  gem "binding_of_caller"
end

middleware stack is broken when updating from 0.7 -> 0.11

Understandably, I expect a project this young to have breaking feature changes. I haven't had time to investigate it too much, but thought you might have some insight as to what changed between 0.7 and 0.11 around adding middleware to the stack. I had been doing as the documentation stated:

middleware << proc {
  use Warden::Manager (...)
}

Unfortunately, I now get complaints about header and env being nil, among other things. Any suggestions, or is this a legit bug?

helpers to the Root url of the controller

I will start this with my example config.ru

require 'scorched'

class SubController < Scorched::Controller
  get '/' do
    url # Would expect this to show http://localhost:9292/root
  end

  get '/branch' do
    absolute # Similar would expect this to show /root
  end

  # The reason for this is to make methods for url that work regardless of where the controller is used
  def branch_path
    absolute('/branch')
    # in reality required to know how it is mounted as correct method would need to be
    # absolute('root/branch')
  end
end

class App < Scorched::Controller
  controller '/root', SubController
end

run App

This was possibly my misunderstanding when reading the docs. So have been considering each Scorched controller as a nice way to build and compose rack applications. Therefore I would expect the subcontroller to be mounted to app and there be a way to get url helpers within a controller. When reading the docs I thought i had found what I was after.

absolute - Returns the absolute URL of the web application root, with the optional argument joined to the end. For example, if you're application was mounted under example.com/myapp/: absolute '/about' #=> /myapp/about

Is there away to get a application url just from specifying the controller extension

# Within subcontroller
relative_url('branch')
# => /root/branch

I imagine there might be a use for both but it comes up alot that I have the desire to redirect to the show path after creating a resource so i would use redirect relative_url("#{resource.id}") and would like that to work when the controller is mounted within an admin controller within an app controller or just mounted on its own for testing purposes.

Benchmarks results for Hello World

Hi @Wardrop

Thanks for Scorched in the first place and have been using it for a project.

Do you happen to have any comments regarding the benchmark made by luislavena and why it performed slower than the other ones based on Rack as well?

Genuinely interested in your opinion and what do you think about it.

Thanks in advance!

Examples of sub-controllers with configuration applied along the way

I am opening this as an issue (even though it is not an issue) because I didn't see any links to a (possible) mailing list. That said, my question is about this line on the README:

Scorched supports sub-controllers to any arbitrary depth, with each controllers configuration, filters, route conditions, etc, applied along the way.

Do you have examples of how this would work? I am asking because this is similar to the approach I have tried on Dynamo but while building an application, it felt clunk. As the application grows, it gets harder for you to know where the request has passed through and it doesn't compose as well. For example, in a single router (controller), you have routes that require authentication and some that does not, and declaring those in a routing-style controller is often too verbose and repetitive.

If you have examples, snippets from real applications, it would be even better (I can provide some too)! In any case, great job!

lib/scorched/request.rb has incorrect require

You updated this file for URI.unescape() obsolescence to CGI.unescape() but didn't update the require.

I believe you need to update lib/scorched/request.rb to require 'cgi' instead of require 'uri'. It took a number of my apps before I ran into one that this caused an issue on since CGI is so ubiquitous.

Flash messages not being set in after block

I am currently trying to set a flash message in an after block

after :status => 404 do
    flash['error'] = 'Page not found'
    redirect '/'
  end

The redirection works but changes to the flash are not being added. I have tested and an empty flash hash and session hash are both correctly available in the error block

render inside a view still renders the layout

At some point I'd like to start providing PRs with code to help with this stuff, but at the moment I only have the time to create issues --

I noticed that when you call a render from inside a view, the layout is still rendered for that subview, which can end up resulting in a quite nested page view. It seems logical that a view rendered inside another view should default to no layout, or :layout => nil. For now, :layout => nil on the render inside the view fixes the issue.

Redirect using POST

When I call redirect, it is using the POST method. For example:

post '/cart/update' do
  redirect '/cart'    # log shows POST /cart
end

IRC channel

Do you have one? Are there any chances to meet authors online to discuss upcoming API extensions? Google Groups seem to duplicate GH issues instead of giving an ability to talk real-time ๐ŸŒต

Adding caught exceptions to env hash

Does Scorched keep track of thrown errors anywhere? I would expect them to be in env['rack.exception'

If not could this feature be added?

I currently have this hack in my base application to make it happen

error do
    env["rack.exception"] = $!
    false
  end

Seems right, needs more refinement

Did you think about helpers and rendering? Do you have any actual code, which could be tested?

Actually I have for long time some general thoughts about how should "ideal web framework" should look and I must say your concept really fits my vision.

I have vacation through most of august, but I definitely come back in September for more. Hope you will like my ideas!

In render method: same dir for template and layout

Hi!

I am trying to replace renee gem by yours (since unfortunately renee gem does not seem to be maintained anymore). I need to render file that is not in the same view directory as the layout. Looking at the source code of the render method, dir is the same for both template file and layout. Do I need to read the file content as a string to apply to the render method or do you have some great idea to solve this issue (for me).

Thanks for your great job.

Finish implementing proper support for accept headers

Requires creating replacement for rack-accept that parses as per the HTTP spec, and which can provide the necessary information to fully support the intended behaviour of the spec.

The parser is completed. Just need to wrap it in a nice API and then use it in Scorched.

singleton can't be dumped error on flash

Getting a singleton can't be dumped error when setting a flash followed by a redirect:

post '/login' do
  flash[:success] = 'nope'
  redirect '/login', 303
end

Stacktrace

/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/session/cookie.rb: in dump
super(::Marshal.dump(str))...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/session/cookie.rb: in encode
super(::Marshal.dump(str))...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/session/cookie.rb: in set_session
session_data = coder.encode(session)...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/session/abstract/id.rb: in commit_session
if not data = set_session(env, session_id, session_data, options)...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/session/abstract/id.rb: in context
commit_session(env, status, headers, body)...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/session/abstract/id.rb: in call
context(env)...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/showexceptions.rb: in call
@app.call(env)...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/logger.rb: in call
@app.call(env)...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/scorched-0.19/lib/scorched/static.rb: in call
response[0] >= 400 ? @app.call(env) : response ...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-accept-0.4.5/lib/rack/accept/context.rb: in call
@app.call(env)...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/methodoverride.rb: in call
@app.call(env)...
/Users/eriklott/.rvm/gems/ruby-2.0.0-p247/gems/rack-1.5.2/lib/rack/head.rb: in call
status, headers, body = @app.call(env)...

Extract private compile method to helper method?

What do you think about extracting the private compile method in controller.rb to a public helper method? The reason I ask is that I need an "except" before filter that will exclude certain paths. Using scorched's compile method for the comparison is the best way to make sure this stays in sync with the way scorched compiles path matching regular expressions.

Provide first-class support for centralised routes and bespoke routing

My intention is to make it easy to define routes in one controller, which refer to methods (actions) in another controller. This may involve considerable refactoring.

For the sake of making it more generic and accommodating to a wider audience, it may be a good idea to create a dispatcher method which can be overridden to suit the exact needs and desires of the developer. This dispatcher method would take some of the responsibility out of the growing action method, making it easier for developers to override routing behaviour.

A significant two-way discussion can be found here: https://groups.google.com/forum/#!topic/scorched/0yzKSrFH6a4

Redirect with flash message

This is a feature request. I would have made it as a pull request but I am struggling to get the tests to work locally. Would it be possible to have the redirect handle and options hash as flash messages

In the controller

def redirect(url, status = (env['HTTP_VERSION'] == 'HTTP/1.1') ? 303 : 302)
  response['Location'] = absolute(url)
  halt(status)
end

To be upgraded to something like

def redirect(url, status = (env['HTTP_VERSION'] == 'HTTP/1.1') ? 303 : 302, **flashes)
  response['Location'] = absolute(url)
  flash.merge! flashes
  halt(status)
end

Good documentation. Please make a wiki.

Hey all! I'm just learning Sinatra. Did you say inspired by Sinatra, or are you using Sinatra code at it's core? I didn't spelunk the code yet. I just noticed no gemspec file and your gemfile refers to one.

Routing Documentation Typo

I noticed a typo on the website's documentation:

http://scorchedrb.com/docs/fundamentals/routing

"A mapping method is also provided as means to access all defined mappings on a controller, but it should be considered read-only for the reasons just stated."

but I think it should be:

"A mappings method is also provided as means to access all defined mappings on a controller, but it should be considered read-only for the reasons just stated."

Symbol route matchers

This perhaps belongs as a plugin rather than as a core feature. However I don't think it would conflict with any existing thinking. Simply it is symbol matchers as available in Roda, http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/SymbolMatchers.html.

Somewhere is set up a regular expression for a particular symbol

match :integer, /\d+/

Then when routing some of the ordering is no longer important

get '/users/:integer' do |id|
  # Stuff
end

get '/users/something' do
  # This route also works
end

This is particularly useful if you want to mix in routes

class App < Scorched::Controller
   include Scorched::Rest(:identifier => /ID\d{3}/)
  get (/terms) { send :terms }
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.