4 ms·
I am not familiar with Ruby except for the casual exposure to it through Sass but couldn't he just go for only if u.profiles.thumbnails.large instead of the
by gotchange 11y ago
I am not familiar with Ruby except for the casual exposure to it through Sass but couldn't he just go for only
if u.profiles.thumbnails.large
instead of these successive checks?
- forgotpwtomain 11y agoNo, because you cannot access thumbnails on profiles = nil.
- gotchange 11y agoWhy can't I access it? I'm genuinely curious about this.
- spoiler 11y agoBecause nil in Ruby is essentially a NULL, or it holds no value. Analogy: It's like asking for a picture of a person that doesn't exist.
- pdkl95 11y agoWhile it's probably a good idea to treat nil as a null pointer that doesn't do anything, it's still an an instance of NilClass and can be used as an object. >> class NilClass • def foo! • puts "bar?" • end • end => :foo! >> nil.foo! bar? Rails uses[1] this with the very-useful #blank? method. [1] https://github.com/rails/docrails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb#L46 https://github.com/rails/docrails/blob/master/activesupport/...
- spoiler 11y agoIt's arguable whether NilClass#blank? is very-useful (the same can be said for #to_a, #to_c, #to_f, #to_h, #to_i, #to_r, and #to_s). I'd call it marginally convenient, because you have to type less, but it doesn't increase usability, and the readability is pretty much the same (or worse, because someone would have to know NilClass responds to blank?, if they don't they might've expected an empty array, or an empty string). Also, why does FalseClass respond to blank? with `true`, but not TrueClass (I didn't know it did this for those two until now)? How is false less blank than true?
- pdkl95 11y agoNilClaass#blank? is incredibly useful because it compliments String#blank? and Array#blank?, which are mostly[1] an alias of #empty?. For most uses where you already know you have a string or array, you probably want to use #empty? directly. The entire point behind #blank? is that it provides a cross-class[2] method that works even when the object doesn't exist (and you get nil instead). This was designed for Rails to simplify situations such as the params[3] (parsed URL query string), which might not have a particular value. # when handling "/action?foo=bar" params[:foo] # => "bar" # when handling "/action?foo=" params[:foo] # => "" # when handling "/action" params[:foo] # => nil With #blank?, you only need one function call to handle both the empty string and nil cases. if params[:foo].blank? # render a static placeholder (never looks at params) else # render Foo's markup, using the value in params[:foo] end If you cared about the empty states other than as a generic "it's blank", other methods may be more appropriate. > why does FalseClass respond to blank? with `true`, but not TrueClass That's a good question. I would have assumed both true and false as not-blank (#blank? => false), as those could both be entered (#present?) values. I suspect it's related to HTML forms only including successful controls[4] in the query string. [1] https://github.com/rails/docrails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb#L100 https://github.com/rails/docrails/blob/master/activesupport/... [2] see: duck-typing [3] http://api.rubyonrails.org/classes/ActionController/Parameters.html http://api.rubyonrails.org/classes/ActionController/Paramete... [4] http://www.w3.org/TR/html401/interact/forms.html#h-17.13.3.2 http://www.w3.org/TR/html401/interact/forms.html#h-17.13.3.2
- spoiler 11y agoI am aware of where it's used, and I am aware it can be convenient, although I am not very enthusiastic about using it. > I suspect it's related to HTML forms only including successful controls Yeah, this is a good point, since an unchecked checkbox isn't transmitted in a standard HTML form, but Rails does add a hidden input element with the value false; so I guess #blank? does somewhat increase readability here, but this could've and should've been fixed in a better way (a few ideas come to mind). Don't get me wrong, I probably used #blank? (and might in the future) just for the sake of convenience. However, it makes me feel guilty when I do, because it could subtly introduce and hide bugs due to lack of more adequate checks.
- code_sterling 11y agoYou can't access that which doesn't exist.
- barrkel 11y agoIf the default behaviour of nil on a send for a method that doesn't exist was to return nil, then nils will flow through your program further than anticipated and debugging will become more difficult. Nils will get into data structures and figuring out when and where they got there will become more work. You'd end up writing a lot more logic testing for nil and throwing an exception when it's found, to avoid the nil propagating further.
- pdkl95 11y agoThe child objects may not exist. "u" is nil (a null pointer, more or less), calling "u.profiles" sends the :profiles message to nil. NilClass probably doesn't have a :profiles method, so this will probably raise a NoMethodError exception. As for why this is a common problem, "u.profiles.thumbnails.large" is is probably a chain of Rails/ActiveRecord model associations[1], which generally map to table rows . If there the "profiles" table doesn't have any rows "WHERE profiles.user_id = users.id", then ActiveRecord will leave a nil[2] (or empty array) in user.profiles. This is true for all foreign key relationships, so you often end up having to do something to protect against null values (no rows) every time you move between models. Sometimes it is possible to work around this by always creating the associated model when the parent object is created, that only works in a few cases. Note: I recommend against doing tricks to auto-build the associated model on first access: that kind of "clever" solution can lead to confusing bugs later on. (I learned that one the hard way) [1] http://guides.rubyonrails.org/association_basics.html http://guides.rubyonrails.org/association_basics.html [2] ibid, 4.1.1.1 "The association method returns the associated object, if any. If no associated object is found, it returns nil."
- gotchange 11y agoThanks for the detailed answer. I think it's safe to assume that Ruby is a bit complicated at least for me but my main point was if he is just interested in checking for the `large` prop (I am not sure about the proper term here in Ruby), I'd expect him to just check for it directly and if this fails, he goes on to the next step as planned and that's why it seemed bit redundant to me esp. I didn't see any exceptions handling done to make use of the fail of previous checks on that chain to instruct the end user or dev accordingly. Again excuse my ignorance of the subject and technical details in Ruby.
- vidarh 11y agoTo elaborate on the other answers: "nil" in Ruby is an instance of NilClass. NilClass has a few methods, but e.g. "thumbnail" is not amongst them, so Ruby looks for a "method_missing" method, doesn't find that either, and then throws an exception, just as if you'd called another non-existent method. To answer a question you didn't ask, though, you can easily write classes to wrap your objects to get pretty much the effect you are asking about. E.g.: class Maybe attr_reader :value def initialize value @value = value end def method_missing *args return self if @value.nil? Maybe.new(@value.send(*args)) end end p Maybe.new(nil).i.can.call.anything.without.exceptions p Maybe.new("Hello foo").gsub("foo","world").value The first call to "p" will print a respresentation of a "Maybe" instance holding "nil" in "@value". The second will print "Hello world", as "gsub" will be called on "Hello foo" since the "Maybe" has a non-nil value. There are any number of implementations of helper classes like this for Ruby, with various levels of popularity. The benefit of course is that you can easily customize the behaviour for your specific needs (e.g. you could create one that will return a default value instead of nil, or you can create one that short-circuits on other values than nil, or any number of alternatives) The advantage of introducing .? as proposed, though, is that it is easier to optimize (no new class or lots of extra objects to create and destroy).
- gotchange 11y agoSo, "nil" is not a primitive value in Ruby explains the extra care taken with these control flow statements in the language. Let's get back to the example, let's say that checking for «u» would return «nil», wouldn't checking for «u.profiles» would result in an exception thrown nonetheless?
- spoiler 11y ago> So, "nil" is not a primitive value in Ruby Almost nothing is a primitive value in Ruby. Not even true/false/numbers are "traditional" primitive values. > Let's get back to the example, let's say that checking for «u» would return «nil», wouldn't checking for «u.profiles» would result in an exception thrown nonetheless? No, because `u.?profile` would return `nil`, then, when you try `nil.?thumbnail` on nil again, it would return nil. So, doing the following, with `u` being `nil` is legal: if u.?profile.?thumbnail # doesn't run if u is nil end
- dragonwriter 11y agoOnly if you extend nil so it responds to "profile", "thumbnails", and "large" messages appropriately. (Or, better, alter the method that you get the user that you are getting "u" back from to return an "nonexistent user" object that responds to "profile" with a "nonexistent profile" object that responds to "thumbnails" with a single-item list containing a "nonexistent thumbnail" object that responds to a "large" method with a "nonexistent image" (or whatever) object that has the right behavior when used where an image object is expected in the code. This approach allows replacing more conditionals with method implementations, and is in that sense more "object oriented" on that null-coalescing which still often requires passing the result to a conditional to handle the null case, and still takes some tricks to distinguish between the terminal method returning nil and something along the chain returning nil, where that differs in the handling.