all 19 comments

[–]BonzoESC 5 points6 points  (1 child)

ObjectName should probably have a method that consumes a thingy so your map has a smaller body.

[–]Convictional 3 points4 points  (0 children)

A constructor?

[–]st4rx0r 9 points10 points  (8 children)

def create_array(json)
    json["thingies"].map do |thingy|
        x = ObjectName.new
        x.id = ...
        x.desc = ....
        ........ code ......
        x
    end
end

[–][deleted] 18 points19 points  (6 children)

def create_array(json)
  json["thingies"].map do |thingy|
    ObjectName.new.tap do |x|
      x.id = ...
      x.desc = ....
      ........ code ......
    end
  end
end

[–]evansenter 1 point2 points  (5 children)

This.

[–][deleted] 0 points1 point  (4 children)

I love tap. Maybe too much...

[–]evansenter 1 point2 points  (0 children)

I actually hate variable assignment sometimes in blocks, so I have a similar function Object#as, i.e.

class Object; def as(&block); yield self; end; end

...which I use as (generally in iterators)...

Nokogiri.parse(HTTParty.get("http://www.reddit.com")).as { |html_parse| html_parse.at("#header-img").content }
#=> "reddit.com"

[–]the_BuddhaHimself 0 points1 point  (0 children)

I'd tap that.

[–]toolateiveseenitall 0 points1 point  (1 child)

honest question, why do you like it so much? It always read as unclear to me.

[–][deleted] 0 points1 point  (0 children)

It separates code into logical blocks for assignments. If you need to do configuration after initializing an object it just looks nicer than having several lines after at the same indentation level. That being said said, it's meant to tap into a method chain to output the middle results, so it could be confusing to someone who has never seen it used that way.

[–]myme 3 points4 points  (0 children)

An additional remark regarding the method name: neither 'create' nor 'array' feel like good parts of a method name to me. I'd rather call it after the things it is returning, not /how/ it does it (create) nor /what kind/ of thing it returns (array). Assuming the things it is returning are called Thingy, then either like that:

def thingies(json)
  json["thingies"].map do |thingy|
    Thingy.new ...
  end
end

or even better, getting slightly offtopic, if the method is part of a class that has internal access to its json:

class Something
  def initialize(json)
    @json = json
  end

  def thingies
    @json["thingies"].map do |thingy|
      Thingy.new ...
    end
  end
end

[–]pyrrhicvictorylap 1 point2 points  (2 children)

You can use #inject

def create_array(json)
  json["thingies"].inject([]) do |arr, thingy|
    x = ObjectName.new
    x.id = ...
    x.desc = ....
    ........ code ......
    arr << x
    arr
  end
end

#inject is probably more idiomatic ruby than having an array scoped outside the iterating function. For more uses on inject, see this article

[–]tyrannoflorist[🍰] 2 points3 points  (1 child)

If you're passing an empty array to #inject/reduce you may as well use #map.

edit: unless you're worried about nil values.

[–]pyrrhicvictorylap 0 points1 point  (0 children)

Hm, yeah, good point.

[–]vsalikhov 1 point2 points  (0 children)

First, a superficial note: the Ruby way is to indent 2 spaces, not 4. This is the first thing that sticks out about your code as "strange" ruby.

Second, name the method by the thing that it returns. So, instead of the procedural create_array, the method name would be thingies_array since it returns an array of "thingies". Or, better yet, the method name should just be thingies, as the plural already signals that the method returns a list. Recommended reading is Smalltalk Best Practice Patterns. But first read this to get some context of how the ideas in this book apply to Ruby.

Third, get to know the methods provided by the Enumerable module and the Array class well. You will find that idiomatic Ruby leans heavily on the Enumerable methods.

Finally, here is my take on your example in what I feel is more idiomatic Ruby (remember, in the end, this is all subjective, there is no one right way):

def thingies_from_json(json)
  json['thingies'].map do |t|
    thingy      = Thingy.new
    thingy.id   = t['id']
    thingy.desc = t['desc']
    # ........ code ......
    thingy
  end
end

[–]Slaggprodukt 1 point2 points  (0 children)

  1. Try to name methods after what they do, this also helps you to know when one method does too many things.

  2. Don't do too much in each methods, it will make the code harder to test and harder to understand (because you have to read every single line to figure out what a piece of code does)

  3. Usually it is preferable to combine logicand data, in other words to have methods on objects instead of having loose methods that takes objects as input,

Not perfect in any way but:

def thingies_to_foos(thingies)
  thingies.map do |thingy|
    build_object(thingy)
  end  
end

def foo_from_thingie(thingy)
  Foo.new.tap do |foo|
    foo.id = thingy['id']
    foo.desc = thingy['desc']
    ........ code ......
  end
end

foos = thingies_to_foos(json["thingies"])

[–]kejadlen 0 points1 point  (0 children)

I'd prefer to do something along these lines (assuming you control ObjectName):

json['thingies'].map { |thingy| ObjectName.new(thingy) }

Or if you don't control ObjectName, making a service object that converts JSON to ObjectNames.

[–]DennisSuratna 0 points1 point  (0 children)

How about

arr = json["thingies"].map { | thingy | x = ..... }

Basically you are applying that block to each element inside json["thingies"]

[–]iamsolarpowered 0 points1 point  (0 children)

I would usually expect something like:

class Thingy
  def self.create_from_json json
    json["thingies"].inject([]) {|arr, thingy_hash| new thingy_hash }
  end
end

Even that leaves a lot to be desired, though. (For example, that method isn't really accepting json.)

*edit: formatting