Вы можете передать себя лямбде в рельсах?

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

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


person bwizzy    schedule 10.07.2009    source источник
comment
Можете ли вы уточнить - эта лямбда вызывается кодом has_attached_file и, следовательно, не в ваших руках? Потому что вы можете передавать параметры в лямбда-выражения при их вызове и можете передавать себя так же легко, как и все остальное.   -  person Matt    schedule 10.07.2009


Ответы (2)


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

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

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

Да, это возможно. В скрепке опция :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
person Ryan McGeary    schedule 10.07.2009
comment
Спасибо, Райан, это именно то, что я искал. - person bwizzy; 11.07.2009
comment
@bwizzy - тогда вам следует щелкнуть галочку рядом с ответом Райана, чтобы отметить, что (1) он ответил на ваш вопрос, и (2) вознаградить его за это. - person rampion; 11.07.2009
comment
Спасибо, извините, это мой первый пост. Спасибо за все быстрые ответы и подробную информацию. - person bwizzy; 11.07.2009
comment
Я столкнулся с той же самой проблемой и решил ее, используя подход, предложенный @ryan-mcgeary. Но мне также нужно использовать delayed_job, поэтому переменные экземпляра не будут работать. Как я мог решить эту проблему? - person Andres; 20.02.2013

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

Локальные переменные в 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, которое используется для превращения блоков в автономные, функционирующие как объекты (которые сами по себе обычно обозначаются как lambdas, procs или Procs).

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"]
person rampion    schedule 10.07.2009