Raamen
Raamen is a Rack and SQLite based MVC web framework. It contains modules to provide basic and necessary features of web app components as well as helpful Rack middleware, cookie manipulation, and CLI to make the development process faster and easier.
contents
- SQLObject
- ControllerBase
- Routing
- Cookies
- Middleware
- Command Line Interface
Installation
Add this line to your application's Gemfile:
gem 'raamen'
And then execute:
$ bundle
Or install it yourself as:
$ gem install raamen
Usage
SQLObject
class Cat < Raamen::SQLObject
belongs_to :owner,
class_name: 'Human',
foreign_key: :owner_id
self.finalize!
end
ControllerBase
class CatsController < Raamen::ControllerBase
def index
@cats = Cat.all
render :index
end
def new
@cat = Cat.new
render :new
end
def create
cat = Cat.new(cat_params)
cat.save
redirect_to '/cats'
end
private
def cat_params
params['cat']
end
end
Routing
router = Raamen::Router.new
router.draw do
get Regexp.new("^/cats$"), CatsController, :index
get Regexp.new("^/cats/new$"), CatsController, :new
post Regexp.new("^/cats$"), CatsController, :create
end
Cookies
class MyController < ControllerBase
def go
session["count"] ||= 0
session["count"] += 1
render :counting_show
end
end
class DogsController < ControllerBase
def create
@dog = Dog.new(params["dog"])
if @dog.save
flash[:notice] = "Saved dog successfully"
redirect_to "/dogs"
else
flash.now[:errors] = @dog.errors
render :new
end
end
end
Middleware
app = Rack::Builder.new do
use Raamen::ShowExceptions
use Raamen::Static
run app
end.to_app
Command Line Interface
To start a new project:
$ raamen n/new cats
To generate a new component:
$ raamen g/generate controller CatsController
To start server:
$ raamen s/start
To start console:
$ raamen c/console
Implementation
SQLObject modules:
module Associatable
def belongs_to(name, = {})
self.[name] = BelongsToOptions.new(name, )
define_method(name) do
= self.class.[name]
key_val = self.send(.foreign_key)
.model_class.where(.primary_key => key_val).first
end
end
def has_many(name, = {})
self.[name] = HasManyOptions.new(name, self.name, )
define_method(name) do
= self.class.[name]
key_val = self.send(.primary_key)
.model_class.where(.foreign_key => key_val)
end
end
end
module Searchable
def where(params)
where_line = params.keys.map do |col|
"#{col}= ?"
end.join(" AND ")
parse_all(DBConnection.execute(<<-SQL, *params.values))
SELECT
*
FROM
#{self.table_name}
where
#{where_line}
SQL
end
end
Template rendering:
def render_content(content, content_type)
raise "double render" if already_built_response?
self.res["Content-Type"] = content_type
self.res.write(content)
self.session.store_session(res)
self.flash.store_flash(res)
self.already_built_response = true
end
def render(template_name)
template_path = File.join(
Dir.pwd,
"app",
"views",
"#{self.class.name.underscore}",
"#{template_name}.html.erb"
)
template_content = File.read(template_path)
render_content(ERB.new(template_content).result(binding), "text/html")
end
Redirecting:
def redirect_to(url)
raise "double render" if already_built_response?
self.res["location"] = url
self.res.status = 302
self.session.store_session(res)
self.flash.store_flash(res)
self.already_built_response = true
end
Cross-Site Request Forgery protection:
def form_authenticity_token
self.res.(
"authenticity_token",
{path: "/", value: self.authenticity_token}
)
self.authenticity_token
end
def self.protect_from_forgery
@@protect_from_forgery = true
end
private
def generate_authenticity_token
SecureRandom.urlsafe_base64(16)
end
def check_authenticity_token
= self.req.["authenticity_token"]
unless && == params["authenticity_token"]
raise "Invalid authenticity token"
end
end
Adding new routes:
def add_route(pattern, method, controller_class, action_name)
self.routes.push(Route.new(pattern, method, controller_class, action_name))
end
def draw(&proc)
self.instance_eval(&proc)
end
[:get, :post, :put, :delete].each do |http_method|
define_method(http_method) do |pattern, controller_class, action_name|
add_route(pattern, http_method, controller_class, action_name)
end
end
Flash cookies:
class Flash
attr_reader :flash, :now
def initialize(req)
flash = req.["_rails_lite_app_flash"]
@now = flash ? Now.new(JSON.parse(flash)) : Now.new({})
@flash = {}
end
def [](key)
self.now[key.to_sym] || self.flash[key.to_sym]
end
def []=(key, val)
self.flash[key.to_sym] = val
end
def store_flash(res)
res.("_rails_lite_app_flash", {path: "/", value: self.flash.to_json})
end
end
Sesssion Cookies:
class Session
attr_reader :cookies
def initialize(req)
= req.["_rails_lite_app"]
= Hash[JSON.parse().map{ |k,v| [k.to_sym, v] }] if
@cookies = || {}
end
def [](key)
self.[key.to_sym]
end
def []=(key, val)
self.[key.to_sym] = val
end
def store_session(res)
res.("_rails_lite_app", {path: "/", value: self..to_json})
end
end
Show exceptions middleware:
class ShowExceptions
attr_reader :app
def initialize(app)
@app = app
end
def call(env)
begin
self.app.call(env)
rescue Exception => e
render_exception(e)
end
end
end
Static assets middleware:
class Static
attr_reader :app, :root, :file_server
def initialize(app)
@app = app
@root = :public
@file_server = FileServer.new(self.root)
end
def call(env)
req = Rack::Request.new(env)
path = req.path
if path.include?("/#{self.root}")
res = self.file_server.call(env)
else
res = self.app.call(env)
end
res
end
end
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/raamen. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.