I'm experimenting with Ruby Shoes. I want controls that become editable as you give them focus, and become text again when they loose it. So far I have the following...
class NameBox < Shoes::Widget
def initialize(model, opts = {})
#model = model
#para = para(value)
self.click{
edit
}
self.keypress{|key|
display if key==:enter
}
end
def display
#ed && #ed.hide
#para.show
#para.text = value
end
def edit
#ed ||= edit_line(value) {|e|
#model.rename(e.text)
}
#para.hide
#ed.text = value
#ed.show
end
def value
#model.name
end
end
used by
class Model
attr_reader :name
def initialize(name)
#name = name
end
def rename(new_name)
#name = new_name
end
end
Shoes.app do
#variable = Model.new("1 2 3")
stack do
10.times{ name_box(#variable) }
end
end
This implementation means if you click on more than one control, they will both be edit boxes.
What I was hoping for was a blur event that would let me change the control back to 'display'. This doesn't exist, so.. how would you implement it?
Assume I will be writing a bunch more controls and they all have to abide by this rule of 'one focused control'
** for Bonus points explain why I can't put:
#ed ||= edit_line(value) {|e|
#model.rename(e.text)
}
#ed.hide()
in the initialize and get #ed to be hidden.
How about this one?
class NameBox < Shoes::Widget
def initialize(model, opts = {})
#model = model
#para = para(value)
self.click{
edit
}
end
def display
#ed && #ed.hide
#para.show
#para.text = value
end
def edit
#ed ||= edit_box(value, height: 30) {|e|
e.text[-1] == "\n" ? display : #model.rename(e.text)
}
#para.hide
#ed.text = value
#ed.show
end
def value
#model.name
end
end
class Model
attr_reader :name
def initialize(name)
#name = name
end
def rename(new_name)
#name = new_name
end
end
Shoes.app do
#variable = Model.new("1 2 3")
stack do
10.times{ name_box(#variable) }
end
end
Related
Okay, this is a little hard to explain but I will try (For starters I am only just learning to code so it may be something super simple I'm missing..)
I created a few classes, I put a name in those classes, I put them in an array, I then chose one at random and try to puts the name, and it outputs blank.
Am I doing this all completely wrong? I've been learning ruby for about 3 months now so I'm sure there is a lot I don't know.
class A
attr :name
def set_name
#name = "Aaa"
end
def get_name
return #name
end
end
class B
attr :name
def set_name
#name = "Bbb"
end
def get_name
return #name
end
end
class C
attr :name
def set_name
#name = "Ccc"
end
def get_name
return #name
end
end
name_a = A.new
name_b = B.new
name_c = C.new
which_name = Array.new
which_name[0] = name_a
which_name[1] = name_b
which_name[2] = name_c
roll = rand(max 3)
puts which_name[roll].get_name
I then chose one at random and try to puts the name, and it outputs
blank.
You never called the #set_name method in your code. You can add this:
name_a.set_name
name_b.set_name
name_c.set_name
Also, you probably want to look into #attr_accessor.
I need to randomly pick a name from an array in Ruby and then check if it uppercase. So far I have:
def namegenerator
return #name.sample
end
def namechecker
if name.upcase then
check = TRUE
else
check = FALSE
end
end
It needs to be as two separate methods like this.
Something like this:
def sample_word(words)
words.sample
end
def upcase?(word)
word == word.upcase
end
And then something like:
words = %w[APPLE banana CherRy GRAPE]
word = sample_word(words)
puts word # e.g. BANANA
puts upcase?(word) # will print true
If you just want to check just the first letter:
names = %w(Kirk mccoy scott Spock)
names.sample.then { |name| [name, name[0] == name[0].upcase] }
#=> ["mccoy", false]
Maybe something like this:
class NameGenerator
def initialize(size, items)
#name = ""
#size = size
#items = items
end
def namegenerator
#name = #items.sample(#size).to_s
end
def namechecker?
#name == #name.upcase
end
def name
#name
end
end
ng = NameGenerator.new 1, ["name", "Name", "NAME"]
ng.namegenerator
puts ng.name, ng.namechecker?
Update
I've posted code without much thinking about abstraction and i think it would be much better to encapsulate name and upper case check to separate class and make it immutable, then make generator class that selects one entity from collection.
class NameGenerator
def initialize(items)
#items = items
end
def next
#items.sample
end
end
class Name
attr_reader :name
def initialize(name)
#name = name
end
def is_uppercase?
#name.match(/\p{Lower}/) == nil
end
end
ng = NameGenerator.new [
Name.new("name"),
Name.new("Name"),
Name.new("NAME"),
Name.new("na-me")
]
name = ng.next
puts name.name, name.is_uppercase?
Hi I try to make my first game in ruby :)
I have two files:
#"game.rb" with code:
class Game
attr_accessor :imie, :klasa, :honor
def initialize(start_scena)
#start = start_scena
end
def name()
puts "Some text"
exit(0)
end
end
and second file
#"game_engine.rb"
require_relative 'game.rb'
class Start
def initialize
#game = Game.new(:name)
end
def play()
next_scena = #start
while true
puts "\n---------"
scena = method(next_scena)
next_scena = scena.call()
end
end
end
go = Start.new()
go.play()
The question is, how can I call class Game.name method from Start.play() class. The game goes deeper, and insted of 'exit(0)' it returns :symbol of another method from "Game" class that should work.
Make start readable for the Game class. DO NOT call exit(0) in your code unless it's really necessary. Instead, use some conditions to make sure the program runs to the end of script.
#"game.rb" with code:
class Game
attr_accessor :imie, :klasa, :honor
attr_reader :start
def initialize(start_scena)
#start = start_scena
end
def name()
puts "Some text"
:round2
end
def round2
puts "round2"
nil
end
end
Use instance#method(...) to get a bounded method to that instance.
#"game_engine.rb"
require_relative 'game.rb'
class Start
def initialize
#game = Game.new(:name)
end
def play()
next_scene = #game.start
while next_scene
puts "\n---------"
scene = #game.method(next_scene)
next_scene = scene.call()
end
end
end
go = Start.new()
go.play()
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names
(Big edit, I got part of the way there…)
I've been hacking away and I've come up with this as a way to specify things that need to be done before attributes are read:
class Class
def attr_reader(*params)
if block_given?
params.each do |sym|
define_method(sym) do
yield
self.instance_variable_get("##{sym}")
end
end
else
params.each do |sym|
attr sym
end
end
end
end
class Test
attr_reader :normal
attr_reader(:jp,:nope) { changethings if #nope.nil? }
def initialize
#normal = "Normal"
#jp = "JP"
#done = false
end
def changethings
p "doing"
#jp = "Haha!"
#nope = "poop"
end
end
j = Test.new
p j.normal
p j.jp
But changethings isn't being recognised as a method — anyone got any ideas?
You need to evaluate the block in the context of the instance. yield by default will evaluate it in its native context.
class Class
def attr_reader(*params, &blk)
if block_given?
params.each do |sym|
define_method(sym) do
self.instance_eval(&blk)
self.instance_variable_get("##{sym}")
end
end
else
params.each do |sym|
attr sym
end
end
end
end
Here's another alternative approach you can look at. It's not as elegant as what you're trying to do using define_method but it's maybe worth looking at.
Add a new method lazy_attr_reader to Class
class Class
def lazy_attr_reader(*vars)
options = vars.last.is_a?(::Hash) ? vars.pop : {}
# get the name of the method that will populate the attribute from options
# default to 'get_things'
init_method = options[:via] || 'get_things'
vars.each do |var|
class_eval("def #{var}; #{init_method} if !defined? ##{var}; ##{var}; end")
end
end
end
Then use it like this:
class Test
lazy_attr_reader :name, :via => "name_loader"
def name_loader
#name = "Bob"
end
end
In action:
irb(main):145:0> t = Test.new
=> #<Test:0x2d6291c>
irb(main):146:0> t.name
=> "Bob"
IMHO changing the context of the block is pretty counter-intuitive, from a perspective of someone who would use such attr_reader on steroids.
Perhaps you should consider plain ol' "specify method name using optional arguments" approach:
def lazy_attr_reader(*args, params)
args.each do |e|
define_method(e) do
send(params[:init]) if params[:init] && !instance_variable_get("##{e}")
instance_variable_get("##{e}")
end
end
end
class Foo
lazy_attr_reader :foo, :bar, :init => :load
def load
#foo = 'foo'
#bar = 'bar'
end
end
f = Foo.new
puts f.bar
#=> bar