Krzysztof Zalewski

zlw@github:pages

Rails: Attr_accessor for HStore

HStore is great feature in PostgreSQL. It allows us to use “mini-noSQL” db in out pg table. Nice!

Rails4 will have this baked-in, but now we have to use great gem activerecord-postgres-hstore.

The only problem is, that there’s no accessor methods. Let’s assume that our model (Product) need price and weight. We will have to write something like this:

product.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Product < ActiveRecord::Base
  def price
    data && data['price']
  end

  def price=(val)
    self.data = (data || {}).merge(price: val)
  end

  def weight
    data && data['weight']
  end

  def weight=(val)
    self.data = (data || {}).merge(weight: val)
  end
end

A lot of repetition. Definetly not DRY. We could use some metaprogramming and create this:

product.rb
1
2
3
4
5
6
7
8
9
10
11
class Product < ActiveRecord::Base
  [:price, :weight].each do |e|
    define_method e do
      data && data[e.to_s]
    end

    define_method :"#{e}=" do |val|
      self.data = (data || {}).merge(e => val)
    end
  end
end

Nice, a lot cleaner. But maybe extract this so we could use it anywhere?

hstore_accessor.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module HstoreAccessor
  extend ActiveSupport::Concern

  module ClassMethods
    # Define accessors method for HStore field
    #
    # @param [Symbol] hstore name of HStore field
    # @param [Array] fields list of accessors methods to define
    def hstore_accessor(hstore, *fields)
      fields.each do |field|
        define_hstore_reader hstore, field
        define_hstore_writer hstore, field
      end
    end


    private

    def define_hstore_reader(hstore, field)
      define_method field do
        send(hstore) && send(hstore)[field]
      end
    end

    def define_hstore_writer(hstore, field)
      define_method :"#{field}=" do |val|
        val = (send(hstore) || {}).merge(field => val)
        send(:"#{hstore}=", val)
      end
    end
  end
end

Now we can do this:

product.rb
1
2
3
4
class Product < ActiveRecord::Base
  include HstoreAccessor
  hstore_accessor :data, :price, :weight
end
Usage
1
2
3
4
5
6
7
p = Product.new

p.price = '$45'
p.price #=> $45

p.weight = '100kg'
p.weight #=> 100kg

Tommorow Night Theme

There is great theme called Tommorow Night. It’s brought for many text editors and IDEs. I use it in RubyMine. Unfortunatelly it’s not really up-to-date. HAML, SASS or CoffeeScript support is broken. That’s why I created modified version.

You can download it and install like this:

1
2
$ cd ~/Library/Preferences/RubyMine40/colors
$ wget https://raw.github.com/gist/2011486/TommorowNight.xml

Get original Tommorow Theme from here

Some examples

Ruby

HAML

SASS

I need to check how this color scheme looks like with Python and Django and make some changes if needed.

Rubyist Wallpaper

It’s another post from Wallpaper series. This time, there is wallpaper especially for Rubyist (if someone does not know, this is common expression for Ruby programmer).

Wallpaper

It’s modified (by me, the worst Photoshop user in the world ™) version of this:

Desktop

I’m thinking about whole series with different programming languages logos – Scala, Clojure, Erlang, Haskell, Python etc. (yes, that’s the list of languages I like and/or want to learn)

But again, I’m the worst Photoshop user in the world

Param_protected Cuts Out Important Attributes

I am huge fan of param_protected which I use instead of attr_accessible/attr_protected.

I think it’s not model thing to allow or deny attributes. Ok, maybe in case of User model I would protect it in controller and model too.

Anyway, it turns out that param_protected cuts out some really important parameters – like action or commit.

It is super easy to fix, but unfortunately it’s not mentioned in documentation. So, let’s add out parameters whitelist.

application_controller.rb
1
2
3
4
5
class ApplicationController < ActionController::Base
  param_accessible [:controller, :action, :id, :utf8, :commit]
  param_accessible [:authenticity_token] # this is for Devise
  param_accessible [:page], only: :index # this is for will_paginate/kaminari
end

controller, action and id are self-explanatory. utf8 and commit are used by Rails forms

I added some additional attributes:

  • authenticity_token which is used by Devise (maybe some other auth gems too)
  • page which is used by will_paginate/kaminari

page is allowed only on index action, because that’s where pagination is used


This post is rewrite of param_protected wycina potrzebne parametry from my previous blog.

Awesome Wallpaper

I came from Linux to Mac about 4-5 months ago. As you might know, in Linux you can change almost everything. Apple don’t go that way with Macs. You take it “as is” and everything you can easily change in GUI is wallpaper. Oh, okay, you can move Dock to the left or right side of the screen ;)

That’s why I am crazy about wallpapers, just like I was about changing GUI on Linux. So, you can like it or not, I will post some awesome wallpaper from time to time.

Wallpaper

Desktop

How to Proper Test Named Scopes

I just read few posts about “how to test named scopes”. It was tons of bullshit. I am asking – why should I use FactoryGirl or raw models and create a lot of unnecessary stuff (objects, db rows etc.) just to test order(:position).first?!

But first, let’s create Post model. Just an example for this note.

post.rb
1
2
3
4
5
6
7
8
9
10
11
class Post < ActiveRecord::Base
  belongs_to :user

  def self.recent
    order('created_at DESC').first
  end

  def self.for_user(user_id)
    where(user_id: user_id)
  end
end

As you see, there are 2 simple scopes. I am not huge fan of Rails’s named scopes – I use plain old class methods instead.

How not to do this

post_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require 'spec_helper'

describe Post do
  context '#recent' do
    it 'should return most recent post' do
      _, post = [Factory(:post), Factory(:post)]

      Post.recent.should == post
    end
  end

  context '#for_user' do
    it 'should return only posts created by given user' do
      post1, post2, _ = [Factory(:post, user_id: 1), Factory(:post, user_id: 1), Factory(:post, user_id: 2)]

      Post.for_user(1).should == [post1, post2]
    end
  end
end

We’ve just created .. let’s count .. 5 objects and made 2 unnecessary db queries.

How to do this

post_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'spec_helper'

describe Post do
  context '#recent' do
    it 'should return most recent post' do
      post = mock_model Post

      Post.should_receive(:order).with('created_at DESC').and_return post
      post.should_receive(:first).and_return post

      Post.recent
    end
  end

  context '#for_user' do
    it 'should return only posts created by given user' do
      post = mock_model Post

      Post.should_receive(:where).with(user_id: 1)
      Post.for_user 1
    end
  end
end

I think it’s useless to test Rails’s elements (like where or order). Rails is really proper tested. Instead we should spec messages between object and passed parameters.

Hello World on New Blog

Hello World!

This is my.. (counting) .. 4th or 5th weblog. I tried wordpress, blogger, tumblr and many others. But as every hacker knows, writing in markdown is much better than shitty WYSIWYG editors.

Plus, you know, having blog on GitHub Pages(!), with Octopress(!), written on a MacBook Pro(!) ?! .. WOOOW! That is so hipster that I could not resist!

What it is going to be about

You know, hacker stuff :)

Mostly Ruby and Ruby on Rails and stuff around – gems, testing etc.

But of course not always. Sometime maybe I’ll write about JavaScript (I’m still trying to learn Ember.js and Backbone.js), Apple/Mac (“wow, look at this great app!” etc.), Python and Django (I’ve been really into it about year ago).

I am still thinkig about my previous blog – should I move posts here from there? Has not yet decided. Meantime, if you want to read it: KrzysztofZalewski::Blog@Blogspot. Oh, yes, it is in Polish.

So, see you next time!