Blog List

William

Sinatra and DataMapper Simple Hit Counter

So I wanted to add a popular posts widget to the side bar and figured it'd be helpful to start tracking the hits on pages.

I did a google search for "ruby hit counter" and "sinatra hit counter" and found this result  that seemed to make it look easy.

ActiveRecord seems like it's very easy, just a few lines. DataMapper wasn't as easy, at least that I saw. Their update_attibute feature is gone and 'save' wasn't playing nice so instead I decided to grab the stat row, create it if it does not exist, and then update it.

Another option is to always create the stat record and that's probablly what I'll do after the db is updated. Then we're just updating the record with one line.

class Stat
   include DataMapper::Resource
   property :id, Serial
   property :hits      , Integer
   belongs_to :post
end

 Then I added to my Post model

has 1, :stat

 And then the code to update the hit count

post ||= Post.get(params[:id]) || halt(404)
unless !request.env["HTTP_USER_AGENT"].match(/\(.*https?:\/\/.*\)/).nil?
  stat ||= Stat.first_or_create(:post_slug => params[:id]) || halt(404)
  stat.update(:hits => (defined?(post.stat.hits) && (post.stat.hits.is_a? Integer)) ? (post.stat.hits+1) : 1)
end

The example I found earlier had said checking if a HTTP_USER_AGENT had a url was a good catch for bots, his check seemed to say unless the agent has one, I'm pretty sure that was wrong so I think mine says if it does not have a url :D

Now we have hit counting on our post, in a seperate table. The future there is to add other stats like Facebook likes and stuff, wonderful.

 


William

Sinatra Shorthand Nested Modules

I was on freenode's Ruby channel earlier today trying to get some code review for this blog (seems harder to come by then in the PHP world) and had only one comment about how my Modules were nested 4 deep making the code quite a big difficult to manage.

The solution was easy.

Shorthand Modules

I was writing all of my Modules out like this:

module Sinatra
  module SimpleRubyBlog
    module Routing
    end
  end
end

So everything was at least 3 or 4 tabs in, not a huge deal, but not great.

What I was told is that once a Module is defined you can refrence it with the short hand, so if we defined the Routing module at the beginning of our code we refrence it directly when adding a new Module, BlogAdmin:

module Sinatra::SimpleRubyBlog::Routing::BlogAdmin
end

Now all of our modules look a lot better.


William

Sinatra Simple Role-based access control RBAC

Every blog needs permissions and roles if you want to have more then a couple users managing it. Trusting random strangers is not good when it comes to your data so we shouldn't let them have access to some important things.

I google searched for "datamapper roles permission" and found this post that lead me to learn about RBAC another term invented to describe something I've always done, awesome! Fresh tags :D

I first ran into this article that seemed to have the start of something, but wasn't complete in any way. So the search continued and I figured I would just try to make it like I normally would, all examples seemed to show that's ok.

The Models

We'll use three models to set this up, Person, Role and Permissions. Roles will belong to Permissions and will be accessible by the Person, and Permissions will belong directly to the Person. This is neat, we can get get person.permissions and person.permissions.roles very easily.

class Person
   include DataMapper::Resource

   property :id, Serial
   property :slug, String   ,  unique_index: true, default: lambda { |resource,prop| resource.title.downcase.gsub " ", "-" }
   property :name      , String   , required: true
   has n, :posts
   has 1.0/0, :roles, :through => Resource
   has 1.0/0, :permissions, :through => Resource
end

class Role
    include DataMapper::Resource

    property :id, Serial
    property :title, String
    property :description, Text

    has n, :permissions, :through => Resource
end

class Permission
    include DataMapper::Resource

    property :id, Serial
    property :title, String
    property :description, Text
    has n, :roles, :through => Resource
    has n, :persons, :through => Resource
end

Making it work 

Last night I couldn't figure out how to properly update related data for tags and categories. It's still a bit borked, but that's for a fresh mind tomorrow. Tonight I managed to get the Permission manager to fully work, allowing you to assign roles and users to permissions.

Some simple ERB in my form that updates or creates the permission

<select id="permissions" multiple name="permissions[]" class="form-control">
  <% Permission.all.each do |perm| %> 
      <option value="<%= perm.id %>" <%= defined?(@person.id) && @person.permissions.include?(perm) ? 'selected' : '' %>><%= perm.title %></option> 
  <% end %> 
</select>

Then an example to update the assigned roles on a permission 

person ||= Permission.first(:id => params[:id]) || halt(404)
roles ||= Role.all(:id => params[:roles]) || halt(404)
person.update(:roles => roles)

And pretty much the same thing to assign permissions to persons

person ||= Person.first(:name => session[:username]) || halt(404)
perms ||= Permission.all(:id => params[:permissions]) || halt(404)
person.update(:permissions => perms)

Conclusion 

It was pretty easy to get a standard Permission and Role setup going in Sinatra with DataMapper. The next step will be to actually do something with those permissions and roles, like restrict access.

Update

Testing showed that new roles would not save, causing all roles to be unset. The solution was to scan the list (sent from select2) for numbers vs strings, new variables are strings, existing ones are numbers.

roles ||= Role.all(:id => add_missing(params[:roles], Roles)) || halt(500)

Then in my helper

def is_number?(object)
  true if Float(object) rescue false
end	

def add_missing array, model
  array.each do |r|
    if !is_number?(r)
    array <<  model.create(:title => r).id
    array.delete r
     end
  end	
  
  array
end

 Now the array being checked for roles will first be checked to see if we need to create a new role.

 I'm certain this can be better!

Looking at it more I'd guess I can extend the model to do this work, so maybe that's next.


William

Sessions not persisting in Chrome on OpenShift

Earlier today we switched to a totally sessions based authentication system. Everything tested fine locally but once it hit OpenShift it stopped working.

I searched for "sinatra not persisting sessions" and found this article that talks about using Rack::Session.

In my case I want to use use Rack::Session::Moneta and I was doing it, but I was also doing enable :sessions

enable :sessions
use Rack::Session::Moneta, store: Moneta.new(:DataMapper, setup: "sqlite3://#{$data_dir}/my_app.db")

I found that if you enable :sessions and use Rack::Session at the same time you have undesired effects. It seems, at least for me, that regular sessions were the only one working.

So erasing enable :sessions in my case worked. Likely a rookie mistake but if you make it like me - heres your answer.

Here is how I am using Sinatra to store sessions in DataMapper

gem 'moneta'

require 'moneta'
require 'rack/session/moneta'

use Rack::Session::Moneta, store: Moneta.new(:DataMapper, setup: "sqlite3://my_app.db")

That's all it took and now sessions are saved in your database instead of users cookies, tho a cookie is still used.


William

Sinatra, MVC not Model View Route

When I last talked about coding Sinatra with a MVC style in mind I ran into a few brick walls. Being only a few days into Sinatra and Ruby at the time I was a little puzzled at the Modules and Classes but I think, to some extent, I've figured it out.

Today I am excited about a MVC style for Sinatra that is reasonable to me. I desire the ability to rapidly create new types of features but the 'core' to all those features is Remove, Edit, Add, List, or what I'll call REAL.

I had already setup 4 sections, Users, Posts, Tags and Categories. I was moving on to the 5 and 6th, Permissions and Roles, and realized I was duplicated the same code once again with very few changes, just a slug, title, description and some data - but the core of the program remained the same.

So you had a lot of duplicate code

Ya I did, that shit sucks.

One thing I've learned while developing my main software for the past 11 years is duplicating code is awful. It's much easier to maintain a package if your properly optimized and are easily adaptable to the changing technology.

Failure to abstract things you'd think would stick around forever could bite you in the but. I realize more now why some applications are 100% abstracted from their base language, things change.

 

What'd you do about it?

We'll I tapped into Sinatra Helpers, these are nifty little buggers. I attached one to my Route Module and bam, I had access to everything. Adding that with my last post yielded a decent result. 

The result, for my Route, was something like this:

module Sinatra::Hi
  module Helpers
    def conf
      @page_title = 'Hi :D'
    end

    def do_index
      "Hello World."
    end

    def do_high
      "Hi :D"
    end
  end

  def self.registered(app)
    app.helpers Helpers

    get_index = lambda do 
      do_index
    end

    get_high = lambda do 
      do_high
    end

    app.namespace "/"  do
      before  { 
        conf
      }

      get  &get_index
      get  '/hi', &get_high
    end
  end
end

While my ideal goal is to have the Helper in a separate file I've considered leaving it as is, the standard Route file is around 80 lines, very decent.

You can see how far I went with the Permissions Module for the Sinatra Decent Blog. The Routes are simply executing Helpers (Controllers) that will then do the hard work. In my case that hard-work is all but a few lines for each thing, check out the Helper that goes with it.

And the conclusion?

I'm pretty happy :D. Code won't be replicated so much, I can still customize as much as I want. Once I figure how my Helper can over-ride another Helper I'll be even happier. 

 

The next day

Always with the next day. You find new stuff, have new ideas, a clear head. Things make more sense.

So today I went to add Roles and found that the Helper defined in your Module is not local, its global. So the 'default_' crap in the Permissions are really, the default. 

I'm thinking a local Class will yield different results, so that's what I'll be doing.



William

Sinatra MVC, Permission Helper and Model

Part of creating the MVC system is to easy manage the permissions required for each section. 

I was working on a really simple RBAC and after a few revisions finally got it to be a decent system that's easy to use.

Restricting Access is the Goal

It could be done 100 ways I'm sure but with this example I want the easiest, the least amount of code with the best of everything (speed, security, optimization).

So I figured why not attach it to the Model and write a Helper to assist with the frontend. The Model would ensure the Person posting would require said permission, the Helper ensures the same thing on the frontend (using a session copy of the Person).

In my Helper

def can role
  role_error unless session[:user].can(role)
end

def role_error
    flash[:error] = t.errors.invalid_permission
    redirect '/'
end

Getting it in the Model

Then I created a Module to extend DataMappers ClassMethods

module Roles
  module ClassMethods
    def can perm
      roles.permissions.count(:title => perm) == 1 ? true : false
    end
  end
end

And made sure DataMapper knew I made that gismo

DataMapper::Model.append_inclusions(Roles::ClassMethods)

Now a simple can('post') would check if the user can post, if not, they go to /. Nothing fancy here.

Since it's a Module Extension you could use it to check if a Person can do a permission pretty easily.

Putting it in action

So that's neat, lets use it somewhere.

app.namespace '/roles'  do
  before  { 
    auth? 
    can('roles')
  }

  get &get_index
end

Now the Person assigned to the user session will be checked for the roles permission and redirected.

Conclusion

I wanted a really simple Role Based Access Control, RBAC, for Sinatra/Ruby. Being only a week or so into Sinatra/Ruby I could only apply my traditonal methods. I hope the'yre Sinatrish.

 

 


William

Sending mail with Mailgun, Sinatra/Ruby and ERB

One of the next steps in the blog was to be able to send mail. I absolutly hate managing mail servers so any time I can use Mailgun I jump on it. The service is quite amazing and lets you do a lot more then just send mail.

I had to figure out how to render ERB views into variables. Then I had to install the mailgun-ruby gem and configure it with my settings.

I stuck to the two things together and got a simple mailer helper that uses a template to style the email. Certainly there are more things to be added here but it's a good start.

The helper:

 module Sinatra
module SimpleRubyBlog
module Helpers
module Mail
def self.send from, to, subject, body, tpl
template = ERB.new File.new(tpl).read, nil, "%"
html = template.result(binding)

message_params = {:from => from,
:to => to,
:subject => subject,
:html => html}

mg_client = Mailgun::Client.new $maingun_apikey
mg_client.send_message $maingun_domain, message_params
end
end
end
end
end

The ERB:

<html>
<head></head>
<body>
<h1>E-mail Test</h1>
<ul>
<li>To: <%= to %></li>
<li>From: <%= from %></li>
<li>Subject: <%= subject %></li>
<li>Body: <%= body %></li>
</ul>
</body>
</html>

Then I call it

 Mail.send('[email protected]', '[email protected]', 'Hi', 'This is cool', './views/emails/email.erb')

Pretty basic!

 

 



William

Ruby Cloudinary Uploading with SEO file names

I learned recently how important it is to keep things on the same domain (not sub) and to seo every possible thing, including image names.

In our case the free Cloudinary does not allow custom domains, but that's ok. I do control the file names for the images, so why not slugify them based on the title?

Here you can see what I did.

if !params['myfile'].nil?
   accepted_formats = [".jpg", ".png", ".gif"]
   halt 500 unless accepted_formats.include? File.extname(params['myfile'][:filename])

new_image = File.open('public/assests/images/' + params['myfile'][:filename], "wb") do |f| f.write(params['myfile'][:tempfile].read) end old_name = 'public/assests/images/' + params['myfile'][:filename] new_name = 'public/assests/images/' + params[:title].slugify + File.extname(params['myfile'][:filename]) File.rename(old_name, new_name) upload = Cloudinary::Uploader.upload(new_name, :use_filename => true) image = upload['secure_url'] File.delete(new_name) end

I've only been doing this for most of the week so I'm sure it could be better.