As far as I know, if all I wanted to do was do "puts" in a console, then I would not be having to ask this question. (However, finally I am asking you all at StackOverflow myself, though I've been visiting for years.)
Here is my issue:
I am trying to create a variable which will be "set" to a specific value upon user click
I am then trying to display that value after it is changed
I can set the value, but it does not get displayed
(Of course, this should work if I am not using Shoes.)
Here is the relevant portion of my code:
class Inits
# Declares all global vars used
def init1
...
end
def initattrib
#id = "###"
#name = "name undef"
alert("Alert: default attrib set")
end
def id
#id
#alert("Alert: id call")
end
def name
#name
#alert("Alert: name call")
end
def id=(newid)
#id = newid
#alert("Alert: id set")
end
def name=(newname)
#name = newname
#alert("Alert: name set")
end
end
Then I am trying to call the id and set it as so:
Shoes.app :width => 800, :height => 600, :resizable => false do
currclass = Inits.new
currclass.init1
currclass.initattrib
.
.
.
id = "123"
name = "new name"
# I declare something to click here
click { currclass.id = id, currclass.name = name }
# Then I try to display it as so:
para currclass.id
para currclass.name
# But of course the value is not displayed -- just the default value
end
... As an aside, I am pretty sure I should be using instance variables and not class variables (#x, not ##x).
Is there some way I can "update on change" ("clock rising edge" is a good analogy) or some other way to call this?
Anyhow, thank you in advance for any advice on what I am not doing correctly. Perhaps there is a misunderstanding.
The first thing you learn using Shoes: It is a metaprogramming jungle.
Shoes has hooks to default procedures, and I'm sure it has one for Class creation as well so Shoes can add its own wizardry to that Class being specified.
Which would mean that a Class - defined outside of Shoes.app - might now work the same as if defined inside Shoes.app.
Try moving everything inside Shoes.app block, I had a lot of problems with pieces of code laying outside of Shoes.app (usually scope problems).
If I understood correctly what you want to do you should do it like that:
class Cos
attr_accessor :co
def initialize(cos)
#co=cos
end
end
Shoes.app :width => 800, :height => 600, :resizable => false do
#cosik = Cos.new("ddd")
#ap=para(#cosik.co)
button "click" do
#cosik.co = "oj"
#ap.text=#cosik.co
end
end
K
Related
Say you have a User class:
class User
attr_accessor :widgets
end
and a Widget:
class Widget
attr_accessor :owner
end
and you assign some widgets to a user:
user = User.new
widget = Widget.new
widget.owner = user
widget2 = Widget.new
widget2.owner = user
user.widgets = [widget, widget2]
Now you have a recursion of user → widgets → owner. user.inspect shows the same user reference once for every widget, cluttering the output:
user.widgets.first.owner.widgets.first.owner
=> #<User:0x00000001cac820 #widgets=[#<Widget:0x00000001ca45f8 #owner=#<User:0x00000001cac820 ...>>, #<Widget:0x00000001c87a20 #owner=#<User:0x00000001cac820 ...>>]>
If we were to reduce this data structure to a hash we'd have:
{ user:
{ widgets: [ { widget: ... },
{ widget: ... } ]
}
}
We could pass this around instead of assigning widget.owner and it would be easy enough to reference the parent user.
I wonder if there's a way to access the parent object through the child without having to assign owner to all child objects, an interface that could work like this:
user = User.new
widget = Widget.new
user.widgets = [widget]
widget.parent
# => #<User:... #widgets=[#<Widget:...>]>
What you're looking for is a custom writer. There is no parent method or equivalent on the Object or BaseObject class, because implementing that would require objects to track every other object that happened to point to it. When you want that functionality though, custom writers make it simple and easy to implement.
class Widget
attr_accessor :owner
end
class User
attr_reader :widgets
def widgets=(widgets)
#widgets = widgets
widgets.each do |widget|
widget.owner = self
end
end
end
user = User.new
widget = Widget.new
user.widgets = [widget]
widget.owner #=> #<User:... #widgets=[#<Widget:...>]>
Note that this custom writer only covers regular assignment, like user.widgets = [widget]. If you wanted to do something like user.widgets << widget, the new widget wouldn't be assigned an owner. If you want to be able to do that, you'll either have to monkeypatch Array like this (not recommended), or you'll have to create a WidgetCollection class that likely inherits from Array. That's what ActiveRecord::Associations does. Speaking of which, if you happen to be using Rails, definitely look into using ActiveRecord to do all this for you. It looks like you're asking about plain old ruby so I'm giving you a vanilla ruby answer.
Thought of sharing the explanation I've come up with. It has no solid proof, but might help.
Firstly, there isn't any problem with loop chaining objects like that. The code wouldn't work fine just like that if there was a problem with loop chains, it would either crash or show an error. So it might be handling these kind of loop references in a way, but it really makes sense if you understand that variables are just references to objects.
I mean when you simply access a User instance user, it doesn't just load up everything inside it recursively. It just does nothing or maybe just takes out the reference. What really sets up the recursion is the inspect method, which recursively inspects all the instance variables inside the instance. But it does handle the deep inspects, with the .....
So your real problem should only be with making inspects look compact. You can override that method, so that it won't recurse, and gives you a nice message. Example :
class User
attr_accessor :widgets
def initialize
#widgets =[]
end
def inspect
"[User:objid=#{object_id};widgets=#{widgets.size}]"
end
end
class Widget
attr_accessor :owner
def inspect
"#[Widget:objid=#{object_id}]"
end
end
The interface can remain the same.
user = User.new
widget = Widget.new
widget.owner = user
widget2 = Widget.new
widget2.owner = user
user.widgets = [widget, widget2]
user.widgets.first.owner.widgets.first.owner
# => #[User:objid=-590412418;widgets=2]
Hi I made it to the lase exercise os Learn Ruby The Hard Way, and I come at the wall...
Here is the test code:
def test_gothon_map()
assert_equal(START.go('shoot!'), generic_death)
assert_equal(START.go('dodge!'), generic_death)
room = START.go("tell a joke")
assert_equal(room, laser_weapon_armory)
end
And here is the code of the file it should test:
class Room
attr_accessor :name, :description, :paths
def initialize(name, description)
#name = name
#description = description
#paths = {}
end
def ==(other)
self.name==other.name&&self.description==other.description&&self.paths==other.paths
end
def go(direction)
#paths[direction]
end
def add_paths(paths)
#paths.update(paths)
end
end
generic_death = Room.new("death", "You died.")
And when I try to launch the test file I get an error:
generic_death = Room.new("death", "You died.")
I tried to set the "generic_death = Room.new("death", "You died.")" in test_gothon_map method and it worked but the problem is that description of the next object is extremely long, so my questions are:
why assertion doesn't not respond to defined object?
can it be done different way then by putting whole object to testing method, since description of the next object is extremely long...
The nature of local variable is that they are, well, local. This means that they are not available outside the scope they were defined.
That's why ruby does not know what generic_death means in your test.
You can solve this in a couple of ways:
define rooms as constants in the Room class:
class Room
# ...
GENERIC_DEATH = Room.new("death", "You died.")
LASER_WEAPON_ARMORY = Room.new(...)
end
def test_gothon_map()
assert_equal(Room::START.go('shoot!'), Room::GENERIC_DEATH)
assert_equal(Room::START.go('dodge!'), Room::GENERIC_DEATH)
room = Room::START.go("tell a joke")
assert_equal(room, Room::LASER_WEAPON_ARMORY)
end
assert the room by its name, or some other identifier:
def test_gothon_map()
assert_equal(START.go('shoot!').name, "death")
assert_equal(START.go('dodge!').name, "death")
room = START.go("tell a joke")
assert_equal(room.name, "laser weapon armory")
end
I know I'm being an idiot here, but I can't think of how this is done. I am creating an app with certain interests and am using a a Wikipedia scrape set up using Nokogiri. I have two inputs: Title and Wikipedia, but want to fill Summary and Content in the data model using the scrape. I want to use the Wikipedia attribute as a variable in a url within a method, but keep getting the error dynamic constant assignment PAGE_URL = "http://en.wikipedia.org/w/i....
I thought that the methods should go in the model, with reference to them in the Create definition under the controller, but this doesn't seem to work.
EDIT
I've just tried taking the constants out of the methods as suggested, but I am still getting a dynamic constant assignment error. My model currently looks like this:
PAGE_URL1 = "http://en.wikipedia.org/w/index.php?title="
PAGE_URL2 = "&printable=yes"
def get_PAGE_URL
PAGE_URL = PAGE_URL1 + self.wikipedia + PAGE_URL2
end
def get_page
page = Nokogiri::HTML(open(PAGE_URL))
end
def get_summary
get_PAGE_URL
self.summary = page.css("p")[0].text
end
def get_full_page
get_PAGE_URL
puts page.css('div#content.mw-body div#bodyContent div#mw-content-text.mw-content-ltr p').each do |p|
self.content = puts p.text
end
end
Constants can't go inside of methods, they must be defined inside of the class' direct scope.
Edit:
For example:
class WikiScraper
PAGE_URL = "http://www.wikipedia.org/"
def scrape
page_num = '5'
my_url = PAGE_URL + page_num
end
end
Let me preface by stating I'm a "new" programmer - an IT guy trying his hand at his first "real" problem after working through various tutorials.
So - here is what I'm trying to do. I'm watching a directory for a .csv file - it will be in this format: 999999_888_filename.csv
I want to return each part of the "_" filename as a variable to pass on to another program/script for some other task. I have come up w/ the following code:
require 'rubygems'
require 'fssm'
class Watcher
def start
monitor = FSSM::Monitor.new(:directories => true)
monitor.path('/data/testing/uploads') do |path|
path.update do |base, relative, ftype|
output(relative)
end
path.create do |base, relative, ftype|
output(relative)
end
path.delete { |base, relative, ftype| puts "DELETED #{relative} (#{ftype})" }
end
monitor.run
end
def output(relative)
puts "#{relative} added"
values = relative.split('_',)
sitenum = values[0]
numrecs = values[1]
filename = values[2]
puts sitenum
end
end
My first "puts" gives me the full filename (it's just there to show me the script is working), and the second puts returns the 'sitenum'. I want to be able to access this "outside" of this output method. I have this file (named watcher.rb) in a libs/ folder and I have a second file in the project root called 'monitor.rb' which contains simply:
require './lib/watcher'
watcher = Watcher.new
watcher.start
And I can't figure out how to access my 'sitenum', 'numrecs' and 'filename' from this file. I'm not sure if it needs to be a variable, instance variable or what. I've played around w/ attr_accessible and other things, and nothing works. I decided to ask here since I've been spinning my wheels for a couple of things, and I'm starting to confuse myself by searching on my own.
Thanks in advance for any help or advice you may have.
At the top of the Watcher class, you're going to want to define three attr_accessor declarations, which give the behavior you want. (attr_reader if you're only reading, attr_writer if you're only writing, attr_accessor if both.)
class Watcher
attr_accessor :sitenum, :numrecs, :filename
...
# later on, use # for class variables
...
#sitenum = 5
...
end
Now you should have no problem with watcher.sitenum etc. Here's an example.
EDIT: Some typos.
In addition to Jordan Scales' answer, these variable should initialized
class Watcher
attr_accessor :sitenum, :numrecs, :filename
def initialize
#sitenum = 'default value'
#numrecs = 'default value'
#filename = 'default value'
end
...
end
Otherwise you'll get uninformative value nil
I'm sure this has to do with the intricacies mentionned in Shoes > Manual > Rules but I just don't get it. If someone would care to explain why #var == nil in the following code ...
I thought I could use visit to switch between different views in my application but that won't work if I lose all state.
class MyShoe < Shoes
url '/', :index
url '/somewhere', :somewhere
def index
#var = para link( "go somewhere", :click => "/somewhere" )
end
def somewhere
para "var = #{#var.inspect}"
end
end
Shoes.app
_why himself has answered this issue, and I'll get to that in a minute. First, the simplest way to pass data (specifically, strings) between different urls is like this:
class MyShoe < Shoes
url '/', :index
url '/somewhere/(\d+)', :somewhere
def index
#var = para link( "What is 2 + 2?", :click => "/somewhere/4" )
end
def somewhere(value)
para "2 + 2 = #{value}"
end
end
Shoes.app
It will match the subgroups of the regex and pass the matching strings as parameters to the method. Occasionally useful, but it gets unwieldy in a hurry. The other solution is to use constants or class variables, as _why explains here:
OK, so fooling around further it looks like all instance variables get
wiped at the beginning of every method within a Shoes subclass.
That's OK I guess. So what's the preferred way to have some data
that's shared from one Shoes URL to another? Passing it from one page
to the next in the URL itself works -- if it's a string. If it's not
a string, what should you use -- ##class_variables?
Sure you could use a class var. Those
are guaranteed to persist throughout
the life of the app. Or a constant.
Also, Shoes comes with SQLite3, data
can be passed there.
In your example, it would look like this:
class MyShoe < Shoes
url '/', :index
url '/somewhere', :somewhere
def index
##var = para link( "go somewhere", :click => "/somewhere" )
end
def somewhere
para "var = #{##var.inspect}"
end
end
Shoes.app