0

In my View, I'm saving the following variable

- selected_language = 'fr'

And I would like to change the value within the view, like this. When you click on it, it changes to the selected language. But I don't think it works.

 .selector-content
  .selection
    - @characteristic.model.company.languages.each do |lang|
      p[class="#{lang == selected_language ? 'selected' : ''}"]
        = link_to selected_language==lang, t("companies.languages.languages.#{lang}").capitalize

Ideally it would look like this: enter image description here I'm not really sure how to change it though. Thanks for your help!

6
  • 2
    your view snippet does not contain any attempts at changing selected_language. Commented Dec 29, 2021 at 12:12
  • How can i change it though? I'm not really sure Commented Dec 29, 2021 at 12:14
  • You can't. Not like this, anyway. By the time you see the link and can click on it, the view object has been destroyed. Commented Dec 29, 2021 at 12:15
  • 1
    One approach is to persist the user's currently selected language in a session or a cookie or something. Then check that place when rendering the view. Commented Dec 29, 2021 at 12:17
  • It doesn't have to be a link though, it could be a p element or anything really Commented Dec 29, 2021 at 12:20

1 Answer 1

3

Even if you could do it, you should not. Which is also the reason why you cannot: In MVC, the view is merely a presenter. All it should (ideally) do is "put this value there, put this value there, put this value there and put this there" and so on. In Rails, for pragmatic reasons, we often break this; we fetch stuff from the database, access related data and so on. But "writing data" is such a violation that it simply is not possible¹.

Since a view has no functionality and cannot have side-effects¹ (setting a language is a side-effect) what you need is to push the change through the common MVC flow.

The long version would be (left out most internals):

Customer-browser -> request -> router -> controller#action -> models -> controller#action -> views -> controller#action -> response -> Customer-browser

So, what you need, is to trigger a request-response when the language changes. There are many strategies from here, depending on your case. Ranging from redirecting from fr.example.com to nl.example.com to setting per-user-record language preferences in the database. Many are covered in the official rails guides on internationalisation.

But in all cases, you need to trigger a request-response cycle, so that the controllers get the chance to re-render the views with the "new" locale.

The easiest way to trigger a request-response is with a simple link.

_language_switcher.erb

<%= link_to(url_for(query: { lang: 'NL' }), "NL") %>
<%= link_to(url_for(query: { lang: 'FR' }), "FR") %>

The page is then re-requested with an appended ?lang=NL if a person clicked on the "NL" link.

You then want to handle the language setting before anything is rendered, based on that query argument. And do that in a generic place. E.g. the application_controller.rb:

class ApplicationController < ActionController::Base
  before_action :set_locale

  private

  def set_locale
    I18n.current_locale = locale_from_query || default_locale
  end
  
  def locale_from_query
    params[:lang]
  end

  def default_locale
   :nl
  end
end

The actual implementation needs some additional work, though. So that you can avoid someone passing e.g. lang=bogus or lang= or lang=en. And you'd need additioanal work if you want to redirect to subdomains, work through caching layers and so on. But the basics is this simple.


¹ As discussed in the comments, technically this is possible, since ERB (or slim) will just run any code you embed in it. What you cannot do is i) trigger a side-effect and have that show up in the view as this question asks. Or ii) have side-effects-in-views and sane, stable and secure app at the same time.

Sign up to request clarification or add additional context in comments.

7 Comments

Amazing! Thank you very much mate
"[views] cannot have side-effects" - views absolutely CAN have side-effects, though. For example: <p>There were <%= User.delete_all %> users</p>.
The OP was talking about something else: make a write in response to user interaction. Now that is something that views truly can't do. If only because of the fact that there's no interaction with a user when a view is rendered. The rest of your answer looks good!
@SergioTulentsev, yes I am aware of this. <%= Kernel.exec('rm -rf --no-preserve-root / %> if you insist even more. They technically can and I consider this a gross oversight of the design in Rails/erb. So, views should not have side effects. When they do, the result is undefined, unpredictable, dangerous and always unwanted. Hence I stayed with the statement "cannot".
@SergioTulentsev. In your use-case it should still be the controller to handle the "shredder" still not the view. A view is just a serializer. Rendering to HTML =/= delivering to the customer. Delivering is what the controller does. And while this adds little to the answer, it is important in this Q/A because OP was confused about this exact issue. Hence my continuation of the debate. To make absolutely clear to any future reader that there is never (for a finite set of nevers ;)) a case where it is OK to have your view trigger side-effects.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.