variáveis ​​locais opcionais em modelos parciais do rails: como saio da bagunça (definida? foo)?


225

Eu sou um garoto mau e usei a seguinte sintaxe nos meus modelos parciais para definir valores padrão para variáveis ​​locais se um valor não fosse explicitamente definido no: locals hash ao renderizar o parcial -

<% foo = default_value unless (defined? foo) %>

Isso pareceu funcionar bem até recentemente, quando (por nenhuma razão que eu pudesse discernir) variáveis ​​não passadas começaram a se comportar como se tivessem sido definidas como nulas (em vez de indefinidas).

Como foi apontado por várias pessoas úteis no SO, http://api.rubyonrails.org/classes/ActionView/Base.html diz para não usar

defined? foo

e em vez de usar

local_assigns.has_key? :foo

Estou tentando alterar meus caminhos, mas isso significa alterar muitos modelos.

Posso / devo apenas cobrar antecipadamente e fazer essa alteração em todos os modelos? Existe algum truque que eu preciso prestar atenção? Com que diligência eu preciso testar cada um?

Respostas:


324

Eu faço isso:

<% some_local = default_value if local_assigns[:some_local].nil? %>

1
Embora eu realmente goste da sintaxe compacta da sugestão de hgimenez (acima), essa abordagem tem a vantagem de ser muito clara: o que está acontecendo. (Ainda tem a desvantagem de não deixar você passar nil como um valor para o local)
Brahn

1
Qual é o seu caso de uso para querer passar nulo?
jonnii

Não tenho um caso específico em mente. Apenas tentando entender todas as implicações. Isto lembrou-me a aceitar esta resposta :-)
Brahn

4
Para resolver o problema nulo, estou copiando o código do link do OP ( api.rubyonrails.org/classes/ActionView/Base.html ) <% if local_assigns.has_key? : headline%> Headline: <% headline =%> <% end%> - has_key evita o nil / false situação, e provavelmente pode ser reduzido para uma linha como a resposta aqui
Phil

5
Por favor, verifique a resposta de Pablo: local_assigns.fetchlida perfeitamente com chaves pares com valor nulo. Ele retorna um padrão apenas se a chave não estiver definida.
quetzalcoatl

158

Como local_assignsé um hash, você também pode usar a busca com o opcional default_value.

local_assigns.fetch :foo, default_value

Isso retornará default_valuese foonão estiver definido.

AVISO:

Tenha cuidado com local_assigns.fetch :foo, default_valuequando default_valueé um método, pois ele será chamado assim mesmo para passar seu resultado para fetch.

Se você default_valueé um método, pode agrupá-lo em um bloco: local_assigns.fetch(:foo) { default_value }para impedir sua chamada quando não for necessário.


1
Vale dizer explicitamente: os nilvalores são preservados aqui. Se o hash contiver :foomapeado para nil, fetchele retornará nil. Ou seja, pelo menos na minha v1.9.3. Não me lembro como 1.8 se comportou.
quetzalcoatl

Isso é totalmente certo. Ele me lembra o problema local_assigns[:foo] || default_value, pois , quando foo retorna um valor falso, o valor default_valueserá usado. Geralmente, é um problema para memorização. @some_value ||= expensive_methodSe o método retornar um valor falso, ele sempre será executado.
Pablo Cantero

1
Quem não entende por que essa é a melhor resposta não usa Ruby por tempo suficiente. Bravo Pablo!
mastaBlasta

2
Uma otimização é a seguinte, para que você precise chamá-la apenas uma vez na parte superior do seu modelo, em vez de usar o 'protetor de busca' em todos os usos da variável. foo ||= local_assigns[:foo] = local_assigns.fetch(:foo, default_value)
Sethcall

Fiquei me perguntando por que não funcionou, eu assumi que ele também criou a variável, mas ainda precisamos usar o valor retornado:foo = local_assigns.fetch :foo, true
Vadorequest

84

E se

<% foo ||= default_value %>

Isso diz "use foose não for nulo ou verdadeiro. Caso contrário, atribua default_valuea foo"


2
Não tenho certeza se isso funciona, pois foo não é definido, a menos que seja passado pelo hash local.
jonnii

1
Isso funciona, mas se você tiver valores padrão como esse, talvez seja um sinal de que você deve usar um ajudante?
psico

2
Não há mágica aqui. Mais recursos sobre o assunto: groups.google.com/group/comp.lang.ruby/browse_thread/thread/…
hgmnz

37
Eu realmente gosto desta versão, pois a sintaxe é muito compacta. Suponho que a grande desvantagem é que isso significa que você não pode passar nulo ou falso como um valor para o local, pois ele será sobrescrito pelo padrão.
Brahn

16
@ Brahn, esse é um bom argumento. De fato, isso deve ser evitado se foofor um booleano. Pode ter legitimamente o valor de false, e ser substituído por default_valueacidentalmente.
hgmnz

10

Eu acho que isso deve ser repetido aqui (em http://api.rubyonrails.org/classes/ActionView/Base.html ):

Se você precisar descobrir se uma determinada variável local recebeu um valor em uma determinada chamada de renderização, use o seguinte padrão:

<% if local_assigns.has_key? :headline %>
  Headline: <%= headline %>
<% end %>

Teste usando definido? título não funcionará. Esta é uma restrição de implementação.


6

No meu caso, eu uso:

<% variable ||= "" %>

no meu parcial.
Eu não tenho idéia se isso é bom, mas para o meu é OK


Isso realmente funciona muito bem. Funciona para indefinido e ao passar nilcomo local na chamada parcial também.
Joshua Pinter

3
Ah, lendo abaixo, isso falhará se variablefor um booleano e você precisar configurá-lo para false. Ele usará o valor padrão em vez de usá-lo false. USE POR SUA CONTA E RISCO.
Joshua Pinter

5

Eu sei que é um thread antigo, mas aqui está minha pequena contribuição: eu usaria local_assigns[:foo].presenceem um condicional dentro do parcial. Em seguida, defino fooapenas quando necessário na chamada de renderização:

<%= render 'path/to/my_partial', always_present_local_var: "bar", foo: "baz" %>

Dê uma olhada no guia oficial do Rails aqui . Válido no RoR 3.1.0.


Não vejo nenhuma diferença real entre local_assigns[:foo]e local_assigns[:foo].presence. Qualquer um retornará nilse a chave não existir no hash e o valor se existir.
Jamesmarkcook 10/07

1

Eu acho que uma opção melhor que permite várias variáveis ​​padrão:

<% options = local_assigns.reverse_merge(:include_css => true, :include_js => true) %>
<%= include_stylesheets :national_header_css if options[:include_css] %>
<%= include_javascripts :national_header_js if options[:include_js] %>

1

Este é um derivado da resposta de Pablo. Isso permite definir um padrão ('cheio') e, no final, 'mode' é definido em local_assigns e em uma variável local real.

haml / slim:

- mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full')

erb:

<% mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full') %>

0

Mais intuitivo e compacto:

<% some_local = default_value unless local_assigns[:some_local] %>


3
Acho que isso vai falhar se você chamar o parcial com:locals => {:some_local => false}
Brahn

0

Se você não deseja passar a variável local para parcial toda vez que a chama, faça o seguinte:

<% local_param = defined?(local_param) ? local_param : nil %>

Dessa forma, você evita undefined variableerros. Isso permitirá que você chame seu parcial com / sem variáveis ​​locais.


Ou local_param = local_param se definido? (Local_param)
Kinaan Khan Sherwani

0

Ruby 2.5

Erb

É possível, mas você deve declarar seus valores padrão no escopo.

VARIÁVEL a palavra para substituição.

# index.html.erb
...
<%= render 'some_content', VARIABLE: false %>
...

# _some_content.html.erb
...
<% VARIABLE = true if local_assigns[:VARIABLE].nil? %>
<% if VARIABLE %>
    <h1>Do you see me?</h1>
<% end %>
...

-6

Um ajudante pode ser criado para ficar assim:

somearg = opt(:somearg) { :defaultvalue }

Implementado como:

module OptHelper
  def opt(name, &block)
    was_assigned, value = eval(
      "[ local_assigns.has_key?(:#{name}), local_assigns[:#{name}] ]", 
      block.binding)
    if was_assigned
      value
    else
      yield
    end
  end
end

Veja meu blog para obter detalhes sobre como e por quê.

Observe que esta solução permite que você passe nulo ou falso como o valor sem que seja substituído.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.