Forum: Ruby BitStruct technique

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
A0c079a7c3c9b2cf0bffebd84dc578b0?d=identicon&s=25 Chuck Remes (cremes)
on 2009-05-14 23:19
(Received via mailing list)
While working on a problem this morning I came up with an interesting
technique for using the BitStruct gem to produce binary strings. After
some hacking around I discovered I didn't need to use BitStruct at
all. Even so I thought I would share what I learned so the technique
doesn't get lost.

Imagine you need to construct a binary string from some arbitrary
object. BitStruct allows you to do this in 3 ways: pass a binary
string directly, pass in a hash, or yield a block. It's easy to extend
this mechanism for parsing your own classes by building on the block
yield initialization mechanism.

hsh = {:a => 1, :b => "foo", :c => "bar"}

class C < BitStruct
   signed :context, 32, "c id"
   char :password, 12*8, "pw"
   char :usr_name, 12*8, "name"

   # important to initialize these values so the
   # block passed to #super is evaluated correctly
   initial_value.context = 0
   initial_value.password = ''
   initial_value.usr_name = ''

   def initialize message
    # parens on #super are important so no args
    # are passed up to the parent; if this part fails
    # then you missed
    super() do |struct|
     struct.context = message[:a]
     struct.password = message[:b]
     struct.usr_name = message[:c]
    end
   end
end

c = C.new hsh  # very clean!
c.inspect

In this example I passed in a hash as my message, but the +message+
variable could have been any object that could be interrogated to
retrieve values for setting the bitstruct fields. I like this
technique because it delegates the responsibility of proper bitstruct
initialization to the class under construction. It nicely encapsulates
that operation which I believe demonstrates the Single Responsibility
principle.

I hope this is of use to someone someday.

cr
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2009-05-14 23:49
(Received via mailing list)
Chuck Remes wrote:
>   def initialize message
>    # parens on #super are important so no args
>    # are passed up to the parent; if this part fails
>    # then you missed
>    super() do |struct|
>     struct.context = message[:a]
>     struct.password = message[:b]
>     struct.usr_name = message[:c]

If you add this line here:

       yield struct if block_given?

then the block initialization can still be used by subclasses or by the
caller of #new:

c = C.new hsh do |struct|
   struct.context = 2
end
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2009-05-14 23:54
(Received via mailing list)
Chuck Remes wrote:
> initialization mechanism.
>   initial_value.context = 0
>     struct.usr_name = message[:c]
> because it delegates the responsibility of proper bitstruct
> initialization to the class under construction. It nicely encapsulates
> that operation which I believe demonstrates the Single Responsibility
> principle.
>
> I hope this is of use to someone someday.
>
> cr

I think I see where you're going with that, but just so others know, the
hash-based initialization is simple (though it does require that the
hash keys match the field names--and avoiding this is probably the point
of your code):

require 'bit-struct'

hsh = { :context => 1, :password => "foo", :usr_name => "bar" }

class C < BitStruct
   signed :context, 32, "c id"
   char :password, 12*8, "pw"
   char :usr_name, 12*8, "name"
end

c = C.new hsh
A0c079a7c3c9b2cf0bffebd84dc578b0?d=identicon&s=25 Chuck Remes (cremes)
on 2009-05-16 22:31
(Received via mailing list)
On May 14, 2009, at 4:53 PM, Joel VanderWerf wrote:

> Chuck Remes wrote:
>>

> I think I see where you're going with that, but just so others know,
>  char :password, 12*8, "pw"
>  char :usr_name, 12*8, "name"
> end
>
> c = C.new hsh

Joel,

that's right. I probably shouldn't have used a hash as my example
since you already have hash-based initialization built in. I meant for
this to be a nice way to pass an arbitrary object to the constructor
so the logic of getting data from that object would be encapsulated in
one spot. I also could have built a temporary hash from the object
argument and passed that to the superclass' constructor but I prefer
the block-based initialization for readability.

And thanks for creating such a neat library. I don't have a need for
it now that I understand my problem domain better, but rest assured it
is now a member of my toolbox for some future problem when I do need it.

cr
This topic is locked and can not be replied to.