Setting instance variables from hash parameters (with defaults)

Discussion in 'Ruby' started by Leslie Viljoen, Oct 26, 2009.

  1. Hi!

    I'm sure I might be reinventing the wheel here.
    I was writing the following ugly code in order to set options (with
    defaults) from a hash:

    def initialize(opts = {})
    @select_max = opts.has_key?:)select_max) ? opts[:select_max] : 10000
    @select_try = opts.has_key?:)select_try) ? opts[:select_try] : 1000
    @min_sleep_sec = opts.has_key?:)min_sleep_sec) ? opts[:min_sleep_sec] : 5
    @max_sleep_sec = opts.has_key?:)max_sleep_sec) ? opts[:max_sleep_sec] : 1800
    @default_sleep = opts.has_key?:)default_sleep) ? opts[:default_sleep] : 10
    ....

    So to avoid that I wrote:

    module Defaulting
    def set_params(defs, params)
    defs.each do |name, val|
    if params.has_key?(name)
    eval "@#{name} = params[name]"
    else
    eval "@#{name} = val"
    end
    end
    end
    end

    So that I could do the much nicer:

    include Defaulting
    def initialize(opts = {})
    defs = {
    :select_max => 10000,
    :select_try => 1000,
    :min_sleep_sec => 5,
    :max_sleep_sec => 1800,
    :default_sleep => 10
    }
    set_params(defs, opts)

    ....

    Note that this allows me to pass options that are explicitly set to nil.

    Now:
    1. Is this functionality already tucked away somewhere else?
    2. How can I get rid of those nasty evals?
    Leslie Viljoen, Oct 26, 2009
    #1
    1. Advertising

  2. I see this is a shorter way:

    module Defaulting
    def set_params(defs, params)
    defs.merge(params).each {|name, val| eval "@#{name} = val"}
    end
    end

    ..still, any way to get rid of that eval?
    Leslie Viljoen, Oct 26, 2009
    #2
    1. Advertising

  3. Hi --

    On Tue, 27 Oct 2009, Leslie Viljoen wrote:

    > Hi!
    >
    > I'm sure I might be reinventing the wheel here.
    > I was writing the following ugly code in order to set options (with
    > defaults) from a hash:
    >
    > def initialize(opts = {})
    > @select_max = opts.has_key?:)select_max) ? opts[:select_max] : 10000
    > @select_try = opts.has_key?:)select_try) ? opts[:select_try] : 1000
    > @min_sleep_sec = opts.has_key?:)min_sleep_sec) ? opts[:min_sleep_sec] : 5
    > @max_sleep_sec = opts.has_key?:)max_sleep_sec) ? opts[:max_sleep_sec] : 1800
    > @default_sleep = opts.has_key?:)default_sleep) ? opts[:default_sleep] : 10
    > ....
    >
    > So to avoid that I wrote:
    >
    > module Defaulting
    > def set_params(defs, params)
    > defs.each do |name, val|
    > if params.has_key?(name)
    > eval "@#{name} = params[name]"
    > else
    > eval "@#{name} = val"
    > end
    > end
    > end
    > end
    >
    > So that I could do the much nicer:
    >
    > include Defaulting
    > def initialize(opts = {})
    > defs = {
    > :select_max => 10000,
    > :select_try => 1000,
    > :min_sleep_sec => 5,
    > :max_sleep_sec => 1800,
    > :default_sleep => 10
    > }
    > set_params(defs, opts)
    >
    > ....
    >
    > Note that this allows me to pass options that are explicitly set to nil.
    >
    > Now:
    > 1. Is this functionality already tucked away somewhere else?
    > 2. How can I get rid of those nasty evals?


    Starting with #2: you can always do:

    instance_variable_set("@#{name}", value)

    For the initialize thing, I would probably do something like this:

    class Whatever
    DEFAULTS = {
    :select_max => 10000,
    :select_try => 1000,
    :min_sleep_sec => 5,
    :max_sleep_sec => 1800,
    :default_sleep => 10
    }

    def initialize(opts)
    DEFAULTS.update(opts).each do |name, value|
    instance_variable_set("@#{name}", value)
    end
    end
    end

    Another thing to keep in mind for similar cases is that hashes return
    nil (unless you override the default) for non-existent keys. So,
    unless you have a hash where nil might be a valid value, you can do:

    h[x] ||= y

    rather than checking for a key.


    David

    --
    The Ruby training with D. Black, G. Brown, J.McAnally
    Compleat Jan 22-23, 2010, Tampa, FL
    Rubyist http://www.thecompleatrubyist.com

    David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)
    David A. Black, Oct 26, 2009
    #3
  4. Whoops, correction:

    On Tue, 27 Oct 2009, David A. Black wrote:
    >
    > def initialize(opts)
    > DEFAULTS.update(opts).each do |name, value|


    That has to be merge, not update.


    David

    --
    The Ruby training with D. Black, G. Brown, J.McAnally
    Compleat Jan 22-23, 2010, Tampa, FL
    Rubyist http://www.thecompleatrubyist.com

    David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)
    David A. Black, Oct 26, 2009
    #4
  5. On Mon, Oct 26, 2009 at 4:42 PM, Leslie Viljoen <> w=
    rote:
    > I see this is a shorter way:
    >
    > module Defaulting
    > =A0 =A0 =A0 =A0def set_params(defs, params)
    > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0defs.merge(params).each {|name, val| eval =

    "@#{name} =3D val"}
    > =A0 =A0 =A0 =A0end
    > end
    >
    > ..still, any way to get rid of that eval?


    One issue with this approach is that with params you can get arbitrary
    instance variables injected, while with the other approach you were
    restricting to the ones you had in defaults. This might be a problem
    or not...

    To get rid of the eval, use instance_variable_set.

    Jesus.
    Jesús Gabriel y Galán, Oct 26, 2009
    #5
  6. Thanks for the replies!
    Leslie Viljoen, Oct 26, 2009
    #6
  7. On Monday 26 October 2009 10:42:01 am Leslie Viljoen wrote:
    > I see this is a shorter way:
    >
    > module Defaulting
    > def set_params(defs, params)
    > defs.merge(params).each {|name, val| eval "@#{name} = val"}
    > end
    > end
    >
    > ..still, any way to get rid of that eval?


    You want instance_variable_set:

    module Defaulting
    def set_params def, params
    defs.merge(params).each {|name, val| instance_variable_set name, val}
    end
    end

    I can think of a few ways to make it easier to use, and I'd suggest actually
    calling the name= method, rather than setting the instance variable directly.
    Here's a rough sketch:

    module Defaulting
    def init_with_vars *vars, &init_block
    defaults = vars.last.kind_of?(Hash) ? vars.pop : {}
    vars = (vars + defaults.keys).uniq
    attr_accessor *vars
    define_method :initialize do |*args, &block|
    if args.last.kind_of? Hash
    defaults.merge(args).each_pair {|name, val| self.send("#{name}=", val)
    }
    end
    if init_block
    init_block.call *args, &block
    end
    end
    end
    end

    I'm fairly sure there's something like this already, some combination of
    something like Struct could work. I do like this usage, though:

    class Foo
    include Defaulting
    init_with_vars :foo, :bar, :baz => 'default baz'
    def bar
    'overriding default bar reader'
    end
    end

    Mostly because for so many classes, I don't need an initialize method at all,
    except as a convenience to set up variables I know I'll need.
    David Masover, Oct 29, 2009
    #7
  8. David A. Black wrote:
    > For the initialize thing, I would probably do something like this:
    >
    > class Whatever
    > DEFAULTS = {
    > :select_max => 10000,
    > :select_try => 1000,
    > :min_sleep_sec => 5,
    > :max_sleep_sec => 1800,
    > :default_sleep => 10
    > }
    >
    > def initialize(opts)
    > DEFAULTS.update(opts).each do |name, value|
    > instance_variable_set("@#{name}", value)
    > end
    > end
    > end


    Note: if you want subclasses to be able override the DEFAULTS, then use
    self.class::DEFAULTS instead of just DEFAULTS. Otherwise DEFAULTS will
    statically resolve to Whatever::DEFAULTS.

    You might also want to add DEFAULTS.freeze, to prevent you accidentally
    mucking them up (as 'update' does :)
    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Oct 29, 2009
    #8
  9. Leslie Viljoen

    Intransition Guest

    On Oct 26, 11:32=A0am, Leslie Viljoen <> wrote:

    > 1. Is this functionality already tucked away somewhere else?
    > 2. How can I get rid of those nasty evals?


    Facets has #instance_assign, however the library is transitioning to
    the more flexible instance_vars.update().

    What I usually do of this kind of thing is use setter methods. That
    way you can control what comes in, how it comes in, and bonus! it's
    well documented. Eg. Along the lines of:

    DEFAULTS =3D {
    :select_max =3D> 10000,
    :select_try =3D> 1000,
    :min_sleep_sec =3D> 5,
    :max_sleep_sec =3D> 1800,
    :default_sleep =3D> 10
    }

    def initialize(opts =3D {})
    DEFAULTS.merge(opts).each do |k,v|
    send("#{k}=3D", v)
    end
    end

    # document me

    attr_accessor :select_max

    # or, if you need more control

    def select_max=3D(val)
    @select_max =3D val
    end

    # etc...

    T.
    Intransition, Oct 29, 2009
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Lauchlan M
    Replies:
    2
    Views:
    486
    John Saunders
    Aug 17, 2003
  2. Erik Johnson
    Replies:
    14
    Views:
    479
    bruno modulix
    Oct 9, 2004
  3. rp
    Replies:
    1
    Views:
    491
    red floyd
    Nov 10, 2011
  4. Pete Elmore

    Two Questions about Hash Defaults

    Pete Elmore, Apr 2, 2005, in forum: Ruby
    Replies:
    4
    Views:
    104
    Pete Elmore
    Apr 4, 2005
  5. Replies:
    3
    Views:
    114
    Roy Pardee
    Mar 11, 2009
Loading...

Share This Page