I'm trying to understand why converting this if-then-else line to the ternary expression (?:) didn't work. I know it may not be the most appropriate code, but I want to understand what's going on.
if a = Artist.find_by(id: params[:artist_id]) then @songs = a.songs else redirect_to(artists_path, alert: "Artist not found") end
The above if-then-else passes the tests, and all it good, but when I tried to make it a ternary expression like so:, it fails to work as I expected.
a = Artist.find_by(id: params[:artist_id]) ? @songs = a.songs : redirect_to(artists_path, alert: "Artist not found")
which gives me the following failure:
1) songs when nested under artists /artists/:artist_id/songs displays the songs with valid artist
Failure/Error: a = Artist.find_by(id: params[:artist_id]) ? @songs = a.songs : redirect_to(artists_path, alert: "Artist not found")
NoMethodError:
undefined method `songs' for nil:NilClass
# ./app/controllers/songs_controller.rb:4:in `index'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/etag.rb:24:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/conditionalget.rb:25:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/head.rb:13:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/session/abstract/id.rb:225:in `context'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/session/abstract/id.rb:220:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/railties-4.2.5/lib/rails/rack/logger.rb:38:in `call_app'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/railties-4.2.5/lib/rails/rack/logger.rb:20:in `block in call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/railties-4.2.5/lib/rails/rack/logger.rb:20:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/methodoverride.rb:22:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/runtime.rb:18:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/lock.rb:17:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/sendfile.rb:113:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/railties-4.2.5/lib/rails/engine.rb:518:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/railties-4.2.5/lib/rails/application.rb:165:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/urlmap.rb:66:in `block in call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/urlmap.rb:50:in `each'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-1.6.4/lib/rack/urlmap.rb:50:in `call'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-test-0.6.3/lib/rack/mock_session.rb:30:in `request'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-test-0.6.3/lib/rack/test.rb:244:in `process_request'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/rack-test-0.6.3/lib/rack/test.rb:58:in `get'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/capybara-2.5.0/lib/capybara/rack_test/browser.rb:60:in `process'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/capybara-2.5.0/lib/capybara/rack_test/browser.rb:35:in `process_and_follow_redirects'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/capybara-2.5.0/lib/capybara/rack_test/browser.rb:21:in `visit'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/capybara-2.5.0/lib/capybara/rack_test/driver.rb:42:in `visit'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/capybara-2.5.0/lib/capybara/session.rb:232:in `visit'
# /home/dapawn/.rvm/gems/ruby-2.3.3/gems/capybara-2.5.0/lib/capybara/dsl.rb:51:in `block (2 levels) in <module:DSL>'
# ./spec/features/songs_spec.rb:13:in `block (4 levels) in <top (required)>'
If a is nil how can it be doing the true part of the ternary expression?
If I put parens around the condition like this, it works fine:
(a = Artist.find_by(id: params[:artist_id])) ? @songs = a.songs : redirect_to(artists_path, alert: "Artist not found")
I figure it must some kind of precedence thing, but I just can't see it. Thanks n advance for taking the time to respond and help me learn.
It is a precedence thing. ?: is higher precedence than =, allowing you to assign depending on the condition:
animal = is_dog ? "dog" : "not a dog"
which is understandably parsed as
animal = (is_dog ? "dog" : "not a dog")
If you want to assign inside the condition, you need the parentheses:
(animal = find_animal("dog")) ? "found dog" : "no dog"
In your case,
a = Artist.find_by(id: params[:artist_id]) ?
@songs = a.songs :
redirect_to(artists_path, alert: "Artist not found")
it will first evaluate Artist.find_by and decide the conditional based on that; if it finds something, it tries to evaluate @songs = a.songs, but a has not been assigned yet. a is awaiting the result of the conditional, and would receive either the value of @songs = a.songs or the value of redirect_to... when the conditional is done.
Stylistically speaking, I would much rather have this left as if ... then ... else ... end, given that you are not using the result of ?:. The ternary conditional operator is typically used when the value returned by the conditional is relevant, and both branches are relatively simple expressions. I would use if ... then ... else ... end if the return value is not used (like in your code), or if the branches consist of more than a simple expression, especially if multi-line or multi-statement:
foo =
if bar
do_something
do_something_else(bar)
final_result
else
do_another_thing
skip_something_else
the_other_final_result
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With