I'm building a site to play games online. I'm working on a little
framework to build turn based games, and now I'm trying to build a
sinatra controller for it.
The purpose of this controller is first to present a list of games
that are present on the system (the framework has an inherited hook to
keep track of which games are available).
These first screens' resources belong in a general folder. When a game
is chosen, though, the resources should be taken from that game's own
folders.
The games could be gems, for example, and are developed independently
of the framework. Each game class contains information about where are
its resources. So, for example I have this file structure:
sintra_app.rb
views/game_list.erb
public/js/main.js
public/css/main.css
In another path:
game1/game1.rb
game1/views/turn_start.erb
game1/public/js/events.js
game1/public/img/card1.jpg
...
So, for a specific request I would check in the session or database
which is the game that is being played, and ask its class for the
public path, but I don't know how to set it for the current request
only. I don't know if it's possible and I'm doing something wrong but
I get NoMethodErrors if I try to use set :public_folder inside a
route definition, so I'm guessing set can only be used in the app or
in configure blocks. Am I right?
So, what would be the best strategy to achieve that?
Thanks,
Jesus.
Regards
Jonathan
2012/1/14 Jesús Gabriel y Galán <jgabrie...@gmail.com>:
> --
> You received this message because you are subscribed to the Google Groups "sinatrarb" group.
> To post to this group, send email to sina...@googlegroups.com.
> To unsubscribe from this group, send email to sinatrarb+...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/sinatrarb?hl=en.
>
Thanks for your answer, I get what you mean. The way I have it now,
each game inherits from a framework class, and this "backend" could be
reused between different controllers:
- A CLI controller
- A GUI controler (i.e.: Shoes)
- A Sinatra controller
just providing views for that type of application.
If I could, I would rather not tie the implementation of the game
logic to Sinatra. Take a look for example to TicTacToe:
require 'gameframework/game/game'
require 'gameframework/game/event'
module TicTacToe
class Game < GameFramework::Game
view_path File.expand_path(File.join(File.dirname(__FILE__), "views"))
initial_view :map
initial_accepted_events :move
[... snip ...]
end
Each type of controller would use the view path and the current view
returned by the game to show the user. Each game could have a set of
views for each type of controller:
tictactoe/cli/map.erb
tictactoe/sinatra/map.erb
tictactoe/sinatra/public/cross.jpg
...
I guess I could just provide a class that inherits from Base, and each
game will provide a subclass, along with the views and resources.
Thanks,
Jesus.
That's how I would do it. A relatively thin layer around the actual
game logic in another class which exposes that over HTTP. As you
develop more games, it might be possible to generate some or all of
your game applications automatically.
Alternatively, if you can determine the game you're in before you
reach sinatra (i.e. via a middleware) you could instead
set :public_folder, proc { request.env['game.public'] }
where the 'game.public' is a path set in the middleware. It needs to
be a middleware, as sinatra's static serving is done before before
filters are invoked. The other alternative, without a middleware,
would be to have the public folder proc determine the game in sinatra.
I'm not sure which is cleaner.
Regards
Jonathan
I tried this but it didn't work:
def view_path
@game ? @game.class.view_path : settings.public_folder
end
configure do
enable :sessions
set :public_folder, Proc.new {view_path}
end
@game is set in a before filter for paths that are in-game.
Jesus.
Yeah, as I said, the static files are served BEFORE the before filter
is processed. So you need to determine the game before you serve
static files to do that. This could be in a middleware, or in the
proc, but you can't rely on the before filters.
Regards
Jonathan
Oh, thanks. I missed to fully understand the implications of what you said.
I'll look into this and see the most convenient way. Could be a good
excuse to learn a bit more about Rack middleware.
Thanks a lot for you help.
Jesus.
Hi again,
I've tried the middleware root. I have a middleware that correctly
detects if there's a game in the session, but the problem I have now
is that I don't know how to access the request from the body of the
proc. I've tried the above but there's no request object.
Also, I would need to have a default value in case the middleware
doesn't set a value there. Using settings.public_folder won't work
since it will recursively call the proc again. Ideas?
Thanks,
Jesus.
Ah, yes, that was me not thinking straight. Since the settings are
class methods, of course they don't have access to a request.
You have a few of options:
1) you could manage the serving of the assets through the same
middleware. Look at how Rack::Static (one of the middlewares bundled
with Rack) does it. It might be possible to modify that for your
needs.
2) If you mount each sinatra application at a different url via rack's
URLMap, you could just have independent public directories for each
game. so:
map "/" do
run RootApp
end
map "/tictactoe" do
run TicTacToeApp
end
etc.
Or via something like
class BaseGameApp < Sinatra::Base
def self.applications
@applications ||= {}
end
def self.namespace(name)
applications[name] = self
end
end
and have each app do
class TicTacToeApp < BaseGameApp
namespace "/tictactoe"
end
then in your config.ru do
run BaseGameApp.applications
Note that the URLMap middleware also supports mapping by hostname, so
you could map to 'tictactoe.yourdomain.com' assuming you have the
necessary CNAMEs or whatever.
Changing the initial portion of the URL or the hostname does have some
benefits, in that you could then send static assets with longer cache
expiry times and still be sure the client would see the right ones.
If you're serving different assets on the same path, it will mess up
caching unless you completely disable it, which makes your application
slower.
3) Implement static file serving yourself. override the static!
method in Sinatra::Base with one that checks for your middleware
defined value for the public folder, or defaults to the settings.
Regards
Jonathan
When I realized this I understood why it was not possible to do what I
wanted with public_folder if I was hosting all games in the same
sinatra app. Even if I made it work for my test, it wouldn't work for
two different users accessing the site.
> You have a few of options:
>
> 1) you could manage the serving of the assets through the same
> middleware. Look at how Rack::Static (one of the middlewares bundled
> with Rack) does it. It might be possible to modify that for your
> needs.
After looking to the middleware options, this is what I ended up doing
yesterday. For the record, this is what is working for me:
require 'gameframework'
class GameStaticResource
def initialize app
@app = app
@file_servers = {}
end
def get_file_server_for_key key
root = GameFramework::Game.available_games[key].resource_path
Rack::File.new(root)
end
def file_server key
@file_servers[key] ||= get_file_server_for_key(key)
end
def call env
path = env["PATH_INFO"]
match = path.match(%r{/resources/(.*?)(/.*)})
if match
key = match.captures[0]
env["PATH_INFO"] = match.captures[1]
file_server(key).call(env)
else
@app.call(env)
end
end
end
Now I just tell the games' views to prefix the resources with
'/resources/#{game_key}' and voila, everything works as expected. I
think it's a good compromise that the sinatra views of the games have
to adhere to this contract for the static resources. I'm now trying to
do this prefixing via a helper, and I'm not managing to make it work,
but I want to dig a little bit more before asking (maybe I'll give up
at some point and ask anyway :) ).
> 2) If you mount each sinatra application at a different url via rack's
> URLMap, you could just have independent public directories for each
> game.
I thought a little bit, as we have already discussed it, about having
each game provide its own sinatra app, but for now I like the above
better.
> 3) Implement static file serving yourself. override the static!
> method in Sinatra::Base with one that checks for your middleware
> defined value for the public folder, or defaults to the settings.
I'd rather have Rack handle the static file serving, they probably do
it better than I ever could ! :D.
Thanks a lot for your help. I love Sinatra and I love this community.
You are all great !
Jesus.