Twitchuserspoints = Struct.new(:name, :points)
xuser = ""
unless ($pointsstructarray.include? xuser.name)
xuser = Twitchuserspoints.new(##username, 100)
$pointsstructarray << xuser.name
$pointsstructarray << xuser.points
else
$pointsstructarray[$pointsstructarray.index(xuser.points)+1] += 1
end
Where to define method 'name'? Also method '+' in else is undefined?
Struct.new(:name, :points) declares a struct itself. To instantiate it, one might either assign the result of above to the variable (or a constant as you do,) and then call Struct#new method on it.
Twitchuserspoints = Struct.new(:name, :points)
x = Twitchuserspoints.new(nil, 0) # arguments are optional
# or, if you don’t want to reuse it
# x = Struct.new(:name, :points).new
x.name = "seasonalz"
x
#⇒ #<struct Twitchuserspoints name="seasonalz", points=0>
Sidenote: using global and class variables is considered a bad practice and anti-pattern in most modern languages, including ruby.
Preliminaries
Without a lot more context, it's hard to see what you're really trying to do or why you're trying to define your behavior in a Struct. That doesn't mean it's wrong to do so; it's just not really obvious why you want to do it this way.
Inherit from Struct
That said, the code for inheriting from a Struct is pretty simple. In your case, you might be able to do something as simple as:
# Define a class that inherits from the Struct class.
class TwitchUser < Struct.new(:name, :points)
end
# Assign the struct to a variable.
twitcher = TwitchUser.new 'foo', 100
# Access the struct members.
twitcher.name #=> "foo"
twitcher.points #=> 100
You can then edit the values by assigning directly to a Struct member:
twicher.points = 200
Re-Open Class to Add Utility Methods
Alternatively, you can re-open your TwitchUser class to extend it with a method to do something with the members. For example:
class TwitchUser
def inc
self.points += 1
end
end
twitcher.inc #=> 101
twitcher.points #=> 101
All-in-One Approach
A very Ruby way to do this would be to declare the class to inherit from Struct, and include any utility methods you might want to operate on the members. For example:
class TwitchUser < Struct.new(:name, :points)
def inc
self.points += 1
end
end
Build Your Array of Struct Objects
Once you have the right type of object to represent your problem domain (whether or not it's a Struct), you can build an array of them. For example:
twitch_users = [twitcher]
twitch_users.first.name
#=> "foo"
twitch_users << TwitchUser.new('bar', 200)
twitch_users.last.name
#=> "bar"
You can then operate on the Array of Struct objects any way you like to find or display the records you want. As just one possible example:
twitch_users.select { |s| s.name.eql?('foo') }.first.points
#=> 101
Related
I want to define a struct which is local to a file/class.
I.e., if I define struct foo in two different files, I want one to be applicable to file a, and one to file b.
Is that doable?
Just as idea: you can use modules or inner classes
module A
MyStruct = Struct.new(:x)
end
module B
MyStruct = Struct.new(:x)
end
or
class A
MyStruct = Struct.new(:x)
end
class B
MyStruct = Struct.new(:x)
end
This way you can use your structs independently as A::MyStruct and B::MyStruct
Just assign the struct class to a local variable (i.e. don't give it a class name).
my_struct = Struct.new(:x)
Caveat
You can't use the struct definition inside classes/modules/functions/methods in the same file.
my_struct = Struct.new(:x)
# This works
my_instance = my_struct.new(123)
class Foo
def foo
# This does NOT work
my_struct.new(123)
end
end
Workaround
You can use the metaprogramming trick called flat scope to mitigate the caveat, but if you decide to do that, you can no longer define methods and classes in the usual way in the whole file, and it's harder to reason about the code because the scope of a method is no longer a clean slate.
my_struct = Struct.new(:x)
Foo = Class.new do
define_method :foo do
# This works, too
my_struct.new(123)
end
end
Working in Ruby, we have to use a 3rd party Framework, which has a class setup something like this:
class Foo
attr_accessor :bar
def initialize()
end
end
class Poorly_Designed_Class
attr_accessor :thing1
attr_accessor :thing2
attr_accessor :thing3
attr_accessor :thing4
attr_accessor :thing5
# through :thing_n .. number defined at runtime
def initialize()
#thing1 = Foo.new
#thing2 = Foo.new
#thing3 = Foo.new
#thing4 = Foo.new
#thing5 = Foo.new
end
end
I don't know how many "things" there are until run time. there could be 5 or their could be 50.
What I would like to do is something like:
pdc = Poorly_Designed_Class.new
for i in 0..numberOfThings do
pdc."thing#{i}".bar = value[i]
end
The above doesn't work.
I've also tried accessing it via:
instance_variable_set("pdc.thing#{i}.bar",value)
I understand that the class should be using an array or hash. Unfortunately I can't do anything about how the class is designed and we have to use it.
Is what i'm trying to do even possible?
You could either try to call the getter (preferably, since it honors encapsulation):
pdc = PoorlyDesignedClass.new
1.upto(number_of_things.times do |i|
pdc.public_send(:"thing#{i}").bar = value[i]
end
or get the instance variable (less preferred, since it breaks encapsulation):
pdc = PoorlyDesignedClass.new
1.upto(number_of_things) do |i|
pdc.instance_variable_get(:"#thing#{i}").bar = value[i]
end
So, you were on the right track, there were just two problems with your code: instance variable names start with an # sign, and . is not a legal character in an identifier.
You're using Object#instance_variable_set incorrectly. The first argument must be a string or a symbol representing the name of an instance variable including the # prefix: e.g. "#thing{i}". However you actually want to get the value of an instance variable and then send #bar= to it. That can be done with Object#instance_variable_get:
1.upto(numberOfThings) { |i| pdc.instance_variable_get("#thing#{i}").bar = value[i] }
That's a bit long and since attr_acessor :thingX defines getter methods, it's usually preferable to call them with Object#public_send instead of directly accessing the instance variable (a getter method might do something else than just returning a value):
1.upto(numberOfThings) { |i| pdc.public_send("thing#{i}").bar = value[i] }
I'd like to add a method to Ruby's Object to reassigns the value associated with it.
For instance, if I had two strings, I could call my method on one of them so that the pointer to the underlying char[] would point to the other's underlying char[].
a = object_a
b = object_b
a == object_b #=> false
a.my_method(object_b)
a == object_b #=> true
Can anyone see a way to accomplish this?
You can't change self, it always points to current object. You can, however, make a proxy around the object. This is quite simple in ruby using SimpleDelegator from the standard library:
require 'delegate'
a = SimpleDelegator.new 'foo'
b = 'bar'
a == b # false
a.__setobj__(b)
a == b # true
You can alias __setobj__ to something nicer, for example:
require 'delegate'
class AwesomeDelegator < SimpleDelegator
alias_method :become, :__setobj__
end
a = AwesomeDelegator.new 'foo'
...
a.become('bar')
class A
attr_accessor :dab
....
end
Now I have an array of instances of A, say
arr = [A.new, A.new, A.new]
And now I want to set a value to all instances of class A present in the array arr. Is there a shortcut in ruby/rails to do this?
In addition, I do have A inherited from ActiveRecord::Base
And my actual need is:
A.find_all_by_some_condition.all.dabs = 2
So, all found objects will have dab set to 2.
Is there shortcut for this?
To get the items of class A from an array you can use select/find_all
arr.select { |el| el.class == A } or arr.select { |el| A === el }
To achieve your actual result though you are looking to assign a value to several objects, not their corresponding class. class A does not define the actual objects it just defines the blueprint that the objects use when getting created. So finding a way to assign a value of all instances of A is not what you are after (although I might have missed the point of what you were asking for)
To assign a value to an array of object this works:
A.find_all_by_some_condition.each { |a| a.dab = 2 }
Perhaps you want to save them after that, now arr.each(&:save) might come in handy. Go look up the ampersand if you don't know it already. Very useful.
You can't do that directly by default, however you could build something like that using Ruby's method_missing.
Two solutions:
Solution 1 - Use a wrapper class
We'll call this class MArray for multi-assign-array.
class MArray
def initialize(inner_array)
#inner = inner_array
end
def method_missing(meth, value)
# Check if assignement, and if it is then run mass-assign
if meth.to_s =~ /^\w+=$/
#inner.each { |itm| itm.send(meth, value) }
else
raise ArgumentError, "MArray: not an assignment"
end
end
end
We also need to add support for MArray in Array, so that the wrapping will take place. We'll call the method mas for "mass-assignment":
class Array
def mas
# Wrap MArray around self
MArray.new(self)
end
end
Usage is simple:
Blob = Struct.new(:dab)
arr = [Blob.new] * 3
arr.mas.dab = 123
arr
=> [#<struct Blob dab=123>, #<struct Blob dab=123>, #<struct Blob dab=123>]
Solution 2 - Create mass-assignment support directly into Array
This is a bit more "dangerous" since we directly modify method_missing in Array. It could create some strange side-effects (for example if method_missing has already been redefined by some other library, or you accidentally call a mass-assign while you didn't mean to).
It works by trying to detect assignments with plural words (words ending with s), and then triggering the mass-assignment:
class Array
def method_missing(meth, *args, &block)
# Check for plural assignment, and as an added safety check also
# see if all elements in the array support the assignment:
if meth.to_s =~ /^(\w+)s=$/ &&
self.all? { |itm| itm.respond_to?("#{$1}=") }
self.each { |itm| itm.send("#{$1}=", *args) }
else
super
end
end
end
Usage then becomes even shorter than with MArray:
Blob = Struct.new(:dab)
arr = [Blob.new] * 3
arr.dabs = 123
arr
=> [#<struct Blob dab=123>, #<struct Blob dab=123>, #<struct Blob dab=123>]
They can be defined like this
Struct.new(:x, :y)
But what can usefully be done with them? Specifically, how can I create an instance of such a struct? This doesn't work
Struct.new(:x => 1, :y => 1)
(you get TypeError: can't convert Hash into String).
I'm using Ruby 1.9.2.
UPDATE:
Good pointers so far, thanks. I suppose the reason I asked this was that I have several times found myself wanting to do this
Struct.new(:x => 1, :y => 1)
just so that I can pass an object around where I can write obj.x instead of, say, instantiating a hash and having to write obj[:x]. In this case I want the structure to be really anonymous - I don't want to pollute my namespace with anything by naming what is returned from the Struct.new call. The closest thing to that, as already suggested is
Struct.new(:x, :y).new(1, 1)
But how do you like them apples? I'm not sure I do. Is it reasonable to expect to be able to define and instantiate an anonymous struct in one go (as part of core Ruby)? I guess when I read the official Ruby docs on Struct.new I assume the word 'anonymous' allows this, but it doesn't.
Struct.new returns a Class, so you can, for example, assign it to a constant like this:
Point = Struct.new(:x, :y)
or subclass it:
class Point < Struct.new(:x, :y)
# custom methods here
# ...
end
In both cases, you can use the resulting class like this:
Point.new(3, 5)
If you don't want to create a specific class (because you need to instantiate an object of that class only once), consider to use OpenStruct instead:
require 'ostruct'
point = OpenStruct.new(:x => 3, :y => 5)
You first create a struct, and then you can create instances of it. It's a way of creating data objects without having to declare a class. Basically it's the same as a hash, but it's more clean to access the objects. You can get stuff out of it by referencing it via ordinary accessor methods.
http://www.ruby-doc.org/core-1.9.3/Struct.html
# Create a structure with a name in Struct
Struct.new("Customer", :name, :address) #=> Struct::Customer
Struct::Customer.new("Dave", "123 Main") #=> #<struct Struct::Customer name="Dave", address="123 Main">
# Create a structure named by its constant
Customer = Struct.new(:name, :address) #=> Customer
Customer.new("Dave", "123 Main") #=> #<struct Customer name="Dave", address="123 Main">
Well, you can use Structs when you don't actually want to write a class with accessors. It's handy to just write
Project = Struct.new(:name)
instead of
class Project
attr_accesor :name
end
As tokland pointed out correctly (thanks!), a Struct also gives you a nice #initialize method automagically. So the following is possible without any further code:
Project = Struct.new(:name)
p = Project.new('Quadriloptic Curves')
I'm hot sure about purpose but Struct.new returns class so
irb(main):001:0> Struct.new(:x,:y)
=> #<Class:0x2914110>
irb(main):002:0> Struct.new(:x,:y).new(1,2)
=> #<struct x=1, y=2>
OpenStruct is probably what you want, but I ran into a situation recently where OpenStruct didn't work because I needed to raise an error when trying to access an undefined attribute. Struct does this:
os = OpenStruct.new
os.x = 1; os.y = 2;
os.z # returns nil
s = Struct.new(:x, :y).new
s.x = 1; s.y = 2;
s.z # raises NoMethodError
Just something to keep in mind.
As for creating instances:
User = Struct.new(:user,:password)
u = User.new("john","secret")