Ruby style programming (DRYness)

Hi everybody,

I have a line of the form:

x = hash1.reject{ |key,value| !value}.keys.join(',')

That worked fine but then I needed to do the same thing for hash2. So I had to add another line like so:

y = hash2.reject{ |key,value| !value}.keys.join(',')

I'm trying go DRY but I can't think of an elegant way to do it -- I'm a relative beginner in ruby. A function is an obvious candidate, but that's probably my php thinking. Any better way to avoid the repetition? Some fancy loop?

Thanks! Sergei

Unless you wanted to add that method to the Hash class, you could easily do:

[hash1, hash2].collect{|hash| hash.reject{ |key,value| ! value}.keys.join(',')}

Which would give you an array of the new hashes. If you wanted to assign these values to x and y:

x,y = [hash1, hash2].collect{|hash| hash.reject{ |key,value| ! value}.keys.join(',')}

Perhaps extending Hash would help:

# lib/hash.rb class Hash   def get_non_empty_keys # use whatever name is appropriate     self.reject{|k, v| !v}.keys.join(',')   end end

# config/environment.rb require "lib/hash.rb"

You can now use the get_non_empty_keys function on your hashes:

x = hash1.get_non_empty_keys y = hash2.get_non_empty_keys

Erol, that's so cool. I like your solution a lot.

What about this one:

I allow users to filter tickets based on the module (I call them components) they report on. The list of components comes in as an array. Here's what I do.

# Get a list of components @components = Component.find :all

# This is the hash that will be used in a SQL statement. It will become clear in a second. @filter_components = {}

# Initialize all components to true (this is the default -- we show tickets for all components) @components.each { |component| @filter_components[] = true }

if request.xhr?   @components.each do |component|     # Shouldn't happen, but if so, empty @filter_components. This means no tickets will be selected.     if params[:filter_components].empty?       @filter_components = {}       break;     end

    if (params[:filter_components]).include?(       @filter_components[] = true     else       @filter_components[] = false     end   end

  # Make sure at least one component is in @filter_components so that we don't have special cases.   @filter_components[0] = true if @filter_components.size == 0 end

# Now comes the line about which I was asking in my previous email. The result of it is used in an IN() part of a SQL query components = @filter_components.reject{ |key,value| ! value}.keys.join(',')

It works but it's a lot of code and the problem is I need to do the same for statuses! Any way to reduce this and make it reusable for statuses? BTW, the reason why I'm not using params[:filter_components] directly in the SQL query (e.g. params[:filter_components].join(',')) is so that the data from the web is not used in a SQL query directly. Make sense?


P.S.: I typed this twice! The first version of this was lost. Bummer.

Why aren't you just doing Ticket.find :all, :conditions => {:component_id => params[:component_ids}} ?


I'd go with this one from Fred. No need to worry about using the params[:component_ids] directly in the finder, the input is being sanitized by Rails before being used in the SQL, much like :conditions => ['field = ?', value] is. It generates an IN() condition if params[:component_ids] is an array or an IS NULL condition if params[:component_ids] is nil.

I thought it would be perfect too, but there's a slight problem. The limitation is that that form assumes everything as AND'ed. This is what I came up with yesterday:

@tickets = Ticket.find :all, :conditions => [           "((assignee_id IS NULL AND reporter_id = :reporter_id) OR assignee_id = :assignee_id)             AND tickets.status_id IN(:statuses) AND tickets.component_id IN(:components)",             {               :assignee_id =>,               :reporter_id =>,               :statuses => @filter_statuses,               :components => @filter_components             }           ]

See that OR? I think because of it I can't use the suggested by Frederick form. And with the form presented here, I couldn't say tickets.component_id = :components. Rails didn't transform the equal sign into an "IN" although it did translate the array of component ids into a comma separated list. But it works nonetheless if I put IN inside the string. Then, if components are empty, it correctly replaces the variable with a NULL. And I think everything is properly escaped as well.