ты можешь передать себя лямбде в рельсах? - PullRequest
2 голосов
/ 10 июля 2009

Я хочу определить метод класса, который имеет доступ к локальной переменной. Так что это будет отличаться для каждого экземпляра класса. Я знаю, что вы можете сделать метод класса динамическим с помощью лямбды, как если бы вы использовали его с named_scope. Но можно ли это сделать для значений, характерных для экземпляра?

В деталях это метод has_attached_file для плагина скрепки в рельсах. Я хочу передать лямбду для хэша стилей, чтобы стили изображений могли основываться на атрибутах объекта, хранящегося в БД. Это возможно?

Ответы [ 2 ]

13 голосов
/ 10 июля 2009

Отказ от ответственности: Во-первых, вопрос ( Можете ли вы передать себя лямбде? ) и проблема, которую вы пытаетесь решить (динамические стили с помощью скрепки), не полностью совпадают вверх. Я не буду отвечать на первоначальный вопрос, потому что он не совсем связан с вашей проблемой, и Рэмпион доблестно ударил его.

Вместо этого я отвечу на ваш вопрос о скрепке.

В деталях это метод has_attached_file для плагина скрепки в рельсах. Я хочу передать лямбду для хэша стилей, чтобы стили изображений могли основываться на атрибутах объекта, хранящегося в БД. Возможно ли это?

Да, это возможно. В канцелярской скрепке опция :styles может использовать Proc. Когда вложение инициализируется, если используется Proc, само вложение передается Proc. Вложение имеет ссылку на связанный объект ActiveRecord, поэтому вы можете использовать его для определения ваших динамических стилей.

Например, ваше объявление has_attached_file может выглядеть примерно так (при условии сценария «Пользователь и аватар», в котором пользователь может настроить размер своего аватара):

class User < ActiveRecord::Base
  has_attached_file :avatar, :styles => lambda { |attachment| 
    user = attachment.instance
    dimensions = "#{user.avatar_width}x#{user.avatar_height}#"
    { :custom => dimensions }
  }
end
9 голосов
/ 10 июля 2009

Хорошо, вы неясны.

Локальные переменные в ruby ​​начинаются со строчной буквы (например, foo, bar или steve) и имеют лексическую область (например, C переменные). Они не имеют ничего общего с «экземпляром класса»

Переменные экземпляра в ruby ​​начинаются с символа @ (например, @foo, @bar или @carl) и находятся в области видимости, когда текущее значение self является объектом, в котором они хранятся.

Если вам нужен метод, который может напрямую обращаться к переменным экземпляра объекта, он называется методом экземпляра. Например, battle_cry и initialize оба являются методами экземпляра:

class Character
  def initialize(name)
    @name=name
  end
  def battle_cry
    @name.upcase + "!!!"
  end
  def Character.default
    new("Leeroy Jenkins")
  end
end

Метод класса, напротив, является методом для объекта Class и не имеет доступа ни к одной из переменных экземпляра этого объекта. В приведенном выше примере default - это метод класса.

Если вам нужен метод (класса или экземпляра), который инициирует изменение или получает значение из текущей области, ruby ​​использует тип обратного вызова, называемый блоком.

class Character
   ATTACKS = [ "Ho!", "Haha!", "Guard!", "Turn!", "Parry!", "Dodge!", "Spin!", "Ha", "THRUST!" ]
   def attack
     ATTACKS.inject(0) { |dmg, word| dmg + yield(word) }
   end
end

person = Character.default
puts person.battle_cry

num_attacks = 0;
damage = person.attack do |saying|
  puts saying
  num_attacks += 1
  rand(3)
end
puts "#{damage} points of damage done in #{num_attacks} attacks"

В приведенном выше примере attack использует ключевое слово yield для вызова переданного блока к этому. Когда мы вызываем attack, локальная переменная num_attacks по-прежнему по объему в блоке мы передаем его (ограниченный здесь do ... end), поэтому мы можем увеличить его. attack умеет передавать значения в блок, здесь они фиксируются в переменной saying. Блок также передает значения вернуться к методу, который отображается как возвращаемое значение yield.

Слово lambda в рубине обычно означает ключевое слово lambda, которое используется превращать блоки в автономные, функционировать как объекты (которые сами обычно обозначается lambda с, proc с или Proc с).

bounce = lambda { |thing| puts "I'm bouncing a #{thing}" }
bounce["ball"]
bounce["frog"]

Так что я думаю, что вы спрашиваете, можете ли вы передать Proc вместо Hash для аргумента метода. И ответ «это зависит». Если метод только когда-либо использует метод #[], тогда да:

class Character
  attr_accessor :stats
  def set_stats(stats)
    @stats = stats
  end
end

frank = Character.new("Victor Frankenstein")
frank.set_stats({ :str => 7, :dex => 14, :con => 9, :int => 19, :wis => 7, :cha => 11 })

monster = Character.new("Frankenstein's Monster")
monster.set_stats(lambda do |stat_name|
  rand(20)
end)

Однако он может использовать некоторые другие Hash специальные методы или вызывать один и тот же ключ несколько раз, которые могут привести к странным результатам:

monster = Character.new("Frankenstein's Monster")
monster.set_stats(lambda do |stat_name|
  rand(20)
end)

monster.stats[:dex] #=> 19
monster.stats[:dex] #=> 1

В этом случае вам может быть лучше кэшировать запросы в промежуточном хэше. Это довольно легко, поскольку Hash может иметь блок инициализатора. Так что, если мы изменим выше:

monster.set_stats(Hash.new do |stats_hash, stat_name|
  stats_hash[stat_name] = rand(20)
end)

monster.stats[:dex] #=> 3
monster.stats[:dex] #=> 3

Результаты кэшируются в хеше

Подробнее о Hash инициализаторах блоков см. ri Hash::new:

-------------------------------------------------------------- Hash::new
     Hash.new                          => hash
     Hash.new(obj)                     => aHash
     Hash.new {|hash, key| block }     => aHash
------------------------------------------------------------------------
     Returns a new, empty hash. If this hash is subsequently accessed
     by a key that doesn't correspond to a hash entry, the value
     returned depends on the style of new used to create the hash. In
     the first form, the access returns nil. If obj is specified, this
     single object will be used for all default values. If a block is
     specified, it will be called with the hash object and the key, and
     should return the default value. It is the block's responsibility
     to store the value in the hash if required.

        h = Hash.new("Go Fish")
        h["a"] = 100
        h["b"] = 200
        h["a"]           #=> 100
        h["c"]           #=> "Go Fish"
        # The following alters the single default object
        h["c"].upcase!   #=> "GO FISH"
        h["d"]           #=> "GO FISH"
        h.keys           #=> ["a", "b"]

        # While this creates a new default object each time
        h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
        h["c"]           #=> "Go Fish: c"
        h["c"].upcase!   #=> "GO FISH: C"
        h["d"]           #=> "Go Fish: d"
        h.keys           #=> ["c", "d"]
...