Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,163
First Language
English
Primarily Uses
N/A
One feature of Ruby that is often overlooked is the ability to use a Hash object as your method's parameter listing. This is a very useful feature that can make your code a lot easier to read, both for yourself and others. Consider the following initialize method on a fictional class:

class Item def initialize(viewport, x, y, width, height, text, alignment=0, font=nil) # Does things endendIf you were to make a new Item object somewhere in your code, it would look like this:

Item.new(@viewport, 30, 40, 150, 25, "Fancy thing", 2, @font)Now, with that many parameters, it's a bit hard to keep track of what is what, wouldn't you say? Let's take a look at how readability can be improved by using a hash as the method's parameter instead:

class Item def initialize(params={}) params = { :viewport => nil, :x => 0, :y => 0, :width => 50, :height => 25, :text => "", :align => 2, :font => Font.new }.merge(params) # Does things endend

What happens here is that we list the default values for the parameters we expect, then we use the merge method for hashes to overwrite these with the parameters we receive, if they exist. This way we can set default options more clearly for all the parameters (if we want to), and it looks cleaner. But what is even better is when we want to make a new item. We can write it like this:

Item.new(viewport: @viewport, x: 30, y: 40, width: 150, height: 25, text: "Fancy thing", align: 2, font: @font)Which means the code is suddenly a lot easier to read, as you know what each parameter actually is. You could work around this using the standard method by naming all the parameters as variables first, then passing them in, but that's a lot of unnecessary work. Also notice that when your parameters is a hash, you don't have to surround the key and value pairs with brackets when putting them in the method.

However, the above could be even more readable. Here's what I like to do: Group sets of parameters together by their logic:

Item.new(viewport: @viewport, x: 30, y: 40, width: 150, height: 25, text: "Fancy thing", align: 2, font: @font)But we have much more choice in how we want to present our parameters clearly this way. You could also do this:

Item.new( :viewport => @viewport, :x => 30, :y => 40, :width => 150, :height => 25, :text => "Fancy thing", :align => 2, :font => @font)And, order of parameters doesn't matter either, so this works fine too:

Item.new( :width => 150, :height => 25, :x => 30, :y => 40, :font => @font, :text => "Fancy thing", :align => 2, :viewport => @viewport)To access these parameters in your method, you'd have to do this as an example:

def initialize(params={}) params = { :viewport => nil, # -- Removed for brevity -- :font => Font.new }.merge(params) @viewport = params[:viewport] @font = params[:font]endThat's a bit cumbersome, to have to write params for each instance var you want to set. As a bonus, I'll show a more convenient way to do this if you find yourself doing this often:

params.each_pair{|key, value| instance_variable_set("@#{key.to_s}", value)}This would write all the parameter values received to their corresponding instance variables on the current class. Of course, if you only want to set some of them, you could modify it and do this:

%w(x y width height viewport).each{|key| instance_variable_set("@#{key}", params[key.to_sym]}What do you think? Got any tips to share about this topic, and code readability? Let's talk about it :)
 
Last edited by a moderator:

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,965
Reaction score
3,073
First Language
French
Primarily Uses
RMMV
Hum so exemple I want to share extra data without having to settle a ton of bracket I use that?

Code:
def something(params)params({paramater}enddef paramsparamater = {#stuff who go here}end
 

Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,163
First Language
English
Primarily Uses
N/A
Not sure I understand completely, but you could do this:

def something(params={}) @params = default_params.merge!(params) puts @params # See resultsenddef default_params { x: 15, y: 15, width: 100 }endThis way you can set the default params in another method if you'd like. It's important that, if you are to use a hash for parameters in a method, you have to write it like this:

Code:
def some_method(params={})end
 
Last edited by a moderator:

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,877
First Language
English
I like the approach.

In Ruby 2.x, they introduced named parameters, which could be done as a hash prior as you have explained.

I haven't had much use for this in most of the code I've written since I don't really work with too many parameters in the first place.

This would be useful when you're making script calls, since I like to do things like this (I've been wanting to implement it, but haven't written any script calls lately that are just full of parameters)

def some_function(arg1, arg2=val2, arg3=val3, arg4=val4, arg5=val5) # ...endAnd if you wanted to specify the value of arg5...well you'll have to specify all of the other arguments as well even if they're just default!It also makes it less flexible because what happens if you wanted to change the default? Now you have a bunch of script calls that have hardcoded the defaults.
 
Last edited by a moderator:

tyler.kendrick

Caffeine Addict
Veteran
Joined
Nov 21, 2014
Messages
52
Reaction score
14
First Language
English
Primarily Uses
I'm really glad that someone else is giving ruby options hashes some love.

Another interesting gain you get from this feature is that you can use static and constant option hashes to share default parameters between methods.

For example:

module Level Options = { level: => 1, min: => 1, max: => 5 }endclass Actor def add_level(options = Level.Options) ... endendclass Enemy def add_level(options = Level.Options) ... endendclass Skill def add_level(actor_id, options = Level.Options) ... endendThis way, you can reuse and encapsulate even default method parameters.

Neat stuff, I like it.

I even prefer it over the new named parameters - simply because it can be reused.
 
Last edited by a moderator:

nio kasgami

VampCat
Veteran
Joined
May 21, 2013
Messages
8,965
Reaction score
3,073
First Language
French
Primarily Uses
RMMV
I found a way for use your aproach this useful for break down some call in braket but I don't use hash I use bracket for compîle my call since using hash  don't work really well

Code:
  def init(battle_flag,bracket =[])    # do not erase_this    bracket =[    start_graphics,    define_actor_position,     define_enemy_position,    define_area,    ].merge(bracket)    #end hash    if battle_flag      define_area      define_actor_position      define_enemy_position if @actor_finish_position_flag      start_graphics    end   reset if @battle_end == true  end
 

TheoAllen

Self-proclaimed jack of all trades
Veteran
Joined
Mar 16, 2012
Messages
6,211
Reaction score
7,484
First Language
Indonesian
Primarily Uses
RMVXA
You know Galenth? I'm very happy to see your easing script which uses this approach. I learnt it and this made my battle system better. If you look at my latest version of battle system, I already implement this stuff :) now I have no worry of using a lot of parameters. So thank you very much!
 

Shaz

Global Moderators
Global Mod
Joined
Mar 2, 2012
Messages
42,509
Reaction score
14,851
First Language
English
Primarily Uses
RMMV
Interesting idea. So that means a script call could contain whatever parameters the caller wanted to pass, in any order, and anything missing would be filled in by the defaults?
 

Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,163
First Language
English
Primarily Uses
N/A
@Shaz: Yeah. For example, take my Font Settings module: 

#==============================================================================# ** Module TDD::Settings::Fonts# ** Module TDD::Settings::Fonts::Colors#------------------------------------------------------------------------------# These modules contain font settings used in other scripts, for easy editing#==============================================================================module TDD module Settings module Fonts module Colors class << self def standard ; Color.new(44, 44, 44, 255) ;end def creamy ; Color.new(255, 247, 237, 255);end def bright ; Color.new(245, 245, 245, 255);end def black ; Color.new(0, 0, 0, 255) ;end def transparent_black; Color.new(0, 0, 0, 125) ;end end end class << self #-------------------------------------------------------------------------- # * Serif #-------------------------------------------------------------------------- def serif(args={}) args = { :color => colors.standard, :out_color => colors.bright, :outline => true, :shadow => false, :bold => false, :size => 28 }.merge(args) # Setup font with args font = Font.new("Day Roman", args[:size]) set_font_args(font, args) return font end #-------------------------------------------------------------------------- # * Serif Bright, Inverted #-------------------------------------------------------------------------- def serif_bright(args={}) args = { :color => colors.bright, :outline => true, :out_color => colors.transparent_black, :shadow => false }.merge(args) return serif(args) end #-------------------------------------------------------------------------- # * Sans Serif #-------------------------------------------------------------------------- def sans_serif(args={}) args = { :color => colors.standard, :out_color => colors.bright, :outline => true, :shadow => false, :bold => false, :size => 18 }.merge(args) # Setup font with args font = Font.new("VL PGothic", args[:size]) set_font_args(font, args) return font end #-------------------------------------------------------------------------- # * Sans Serif Bright, Inverted #-------------------------------------------------------------------------- def sans_serif_bright(args={}) args = { :color => colors.creamy, :outline => true, :out_color => colors.transparent_black, :shadow => false }.merge(args) return sans_serif(args) end #-------------------------------------------------------------------------- # * Colors Access #-------------------------------------------------------------------------- def colors TDD::Settings::Fonts::Colors end #-------------------------------------------------------------------------- # * Set Font Args in Bulk #-------------------------------------------------------------------------- def set_font_args(font, args) args.each{|key, val| font.send("#{key}=", val)} end end end endend 
When I set fonts for bitmaps, I use TDD::Settings::Fonts.serif, for instance. But sometimes I want it bold. Then I can do:

TDD::Settings::Fonts.serif(bold: true)Other times I just want shadows on, and then it'd be

TDD::Settings::Fonts.serif(shadow: true)With a standard approach, I'd have to go through multiple parameters I'm not using in this instance :) In a script call scenario, it's also really good and readable:

TDD::Creature_Compendium.register( :creature => :grotesque_slime, :hint => :weakness, :show_toast => true)It makes it readable and comprehensible straight in the code editor in Ace, which is super helpful even when I wrote the stuff myself :)
 
Last edited by a moderator:

tyler.kendrick

Caffeine Addict
Veteran
Joined
Nov 21, 2014
Messages
52
Reaction score
14
First Language
English
Primarily Uses
I really have nothing more of value to contribute; but I just wanted to comment on the code clarity of @Galenmereth's Settings Module.  That thing is beautiful.

Any formal standards you adhere to that I could read up on? 
 

Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,163
First Language
English
Primarily Uses
N/A
The style is a combination of Rails source code and just stuff I've been picking up during my own development to increase readability for other people on projects, and I'm afraid I don't have written down a comprehensive standard yet. I should though, for my own sake as well :) Maybe I'll make a GitHub wiki page on it.
 
Last edited by a moderator:

tyler.kendrick

Caffeine Addict
Veteran
Joined
Nov 21, 2014
Messages
52
Reaction score
14
First Language
English
Primarily Uses
Just wanted to add that I have been using this method - paired with another pattern, to enable some nice extra extensibility to classes.

For example, if you have the following class with initialization options:

class Messenger # Allows default to be overridden/overloaded def self.options return { before => method:)before), completed => method:)completed) } end # Allows default to be overridden/overloaded def self.before msgbox("Before") end # Allows default to be overridden/overloaded def self.completed msgbox("After") end # Assigns default. Options = Messenger.options def initialize(options = {}) @options = Messenger::options.merge(options || {}) end def invoke(*args, &block) @options[:before].call(*args, &block) do_work(*args, &block) @options[:completed].call(*args, &block) end private # Requires options to contain :work. def do_work(*args, &block) @options[:work].call(*args, &block) endend # MessengerIf you create an instance of the Messenger object, it will throw an exception unless the options hash contains the ":work" symbol. 

messenger = Messenger.new:)work => method:)msgbox))messenger.invoke("I'm working")# >> output:# >> "Before"# >> "I'm working"# >> "After"This is fine and desirable.  

Another option is to override the default options to include a value for work:

class << self alias_method:)base_options, :options)enddef self.options return base_options.merge:)work => method:)msgbox))endThis will change the default value of @options[:work] to now invoke msgbox for all derivations.

However, sometimes it is more beneficial to use an instance method in its place.

Simply extending the class with the following code will enable even greater extensibility by branching default overrides to derived instances.

def option(symbol) @options[symbol] || method(symbol)enddef work(*args, &block) raise NotImplementedError.newendThen change the references to "@options[:symbol]" to "option:)symbol)".

Now, you can subclass Messenger to invoke the desired behavior:

class WorkingMessenger < Messenger def work(*args, &block) msgbox("I'm working") endendThis will now change the default output for all derivations and instances of WorkingMessenger to be "I'm working" - and still be overridden in initialize.

Neat stuff.  I like option hashes.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,877
First Language
English
Customizable parameters is important for me.

def eval_some_formula(formula, a, b, s=$game_switches, v=$game_variables) eval(formula)endThis is the general pattern I use for formula evaluation in RPG Maker.I've defined a couple "common" short-hand variables so you can quickly access objects and write clean formulas.

v[1] == 2 ? a.atk * 4 : a.atk * 2Simple damage formula that allows me to check a variable's value to determine my damage.But what happens if you want to add more variables? Or you want to change the names?

The way I've done it now is hardcode the variables.

Instead, I'd like to separate them into something that you can customize as needed, preferably via aliasing as well so you can add your own custom variables that are available to formulas.

Moving the variables into a method should make this more flexible.
 

Latest Threads

Latest Posts

Latest Profile Posts

Don't think I can ignore this anymore, but it only happens like once a day. Not sure why my entire pc slows down though, its using a lot of memory but not compared to my max. Seem the thread about updating the nwjs it a while ago, it just looked like a pain/tldr :p
nwjs.PNG
Quite the versatile cast so far :p

chars.PNG

Edit: Sprites are made by Alexdraws and TheMightyPalm. I just edited them.
Degica Games Turn Komodo | RPG Maker News #77

Well, rats. Was really looking forward to trying out FPS Creator, but trying to install and set it up was pretty much impossible for my tiny brain to comprehend. So much for that, then.

Forum statistics

Threads
112,427
Messages
1,068,197
Members
146,082
Latest member
khaotom
Top