I have a data structure that uses the Set class from the Ruby Standard Library. I'd like to be able to serialize my data structure to a JSON string.
By default, Set serializes as an Array:
>> s = Set.new [1,2,3]
>> s.to_json
=> "[1,2,3]"
Which is fine until you try to deserialize it.
So I defined a custom to_json method:
class Set
def to_json(*a)
{
"json_class" => self.class.name,
"data" => {
"elements" => self.to_a
}
}.to_json(*a)
end
def self.json_create(o)
new o["data"]["elements"]
end
end
Which works great:
>> s = Set.new [1,2,3]
>> s.to_json
=> "{\"data\":{\"elements\":[1,2,3]},\"json_class\":\"Set\"}"
Until I put the Set into a Hash or something:
>> a = { 'set' => s }
>> a.to_json
=> "{\"set\":[1,2,3]}"
Any idea why my custom to_json doesn't get called when the Set is nested inside another object?
The first chunk is for Rails 3.1 (older versions will be pretty much the same); the second chunk is for the standard non-Rails JSON. Skip to the end if tl;dr.
Your problem is that Rails does this:
[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
klass.class_eval <<-RUBY, __FILE__, __LINE__
# Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
def to_json(options = nil)
ActiveSupport::JSON.encode(self, options)
end
RUBY
end
in active_support/core_ext/object/to_json.rb. In particular, that changes Hash's to_json method into just an ActiveSupport::JSON.encode call.
Then, looking at ActiveSupport::JSON::Encoding::Encoder, we see this:
def encode(value, use_options = true)
check_for_circular_references(value) do
jsonified = use_options ? value.as_json(options_for(value)) : value.as_json
jsonified.encode_json(self)
end
end
So all the Rails JSON encoding goes through as_json. But, you're not defining your own as_json for Set, you're just setting up to_json and getting confused when Rails ignores something that it doesn't use.
If you set up your own Set#as_json:
class Set
def as_json(options = { })
{
"json_class" => self.class.name,
"data" => { "elements" => self.to_a }
}
end
end
then you'll get what you're after in the Rails console and Rails in general:
> require 'set'
> s = Set.new([1,2,3])
> s.to_json
=> "{\"json_class\":\"Set\",\"data\":{\"elements\":[1,2,3]}}"
> h = { :set => s }
> h.to_json
=> "{\"set\":{\"json_class\":\"Set\",\"data\":{\"elements\":[1,2,3]}}}"
Keep in mind that as_json is used to prepare an object for JSON serialization and then to_json produces the actual JSON string. The as_json methods generally return simple serializable data structures, such as Hash and Array, and have direct analogues in JSON; then, once you have something that is structured like JSON, to_json is used to serialize it into a linear JSON string.
When we look at the standard non-Rails JSON library, we see things like this:
def to_json(*a)
as_json.to_json(*a)
end
monkey patched into the basic classes (Symbol, Time, Date, ...). So once again, to_json is generally implemented in terms of as_json. In this environment, we need to include the standard to_json as well as the above as_json for Set:
class Set
def as_json(options = { })
{
"json_class" => self.class.name,
"data" => { "elements" => self.to_a }
}
end
def to_json(*a)
as_json.to_json(*a)
end
def self.json_create(o)
new o["data"]["elements"]
end
end
And we include your json_create class method for the decoder. Once that's all properly set up, we get things like this in irb:
>> s = Set.new([1,2,3])
>> s.as_json
=> {"json_class"=>"Set", "data"=>{"elements"=>[1, 2, 3]}}
>> h = { :set => s }
>> h.to_json
=> "{"set":{"json_class":"Set","data":{"elements":[1,2,3]}}}"
Executive Summary: If you're in Rails, don't worry about doing anything with to_json, as_json is what you want to play with. If you're not in Rails, implement most of your logic in as_json (despite what the documentation says) and add the standard to_json implementation (def to_json(*a);as_json.to_json(*a);end) as well.
Here is my approach to getting to_json method for custom classes which most probably wouldn't contain to_a method (it has been removed from Object class implementation lately)
There is a little magic here using self.included in a module. Here is a very nice article from 2006 about module having both instance and class methods http://blog.jayfields.com/2006/12/ruby-instance-and-class-methods-from.html
The module is designed to be included in any class to provide seamless to_json functionality. It intercepts attr_accessor method rather than uses its own in order to require minimal changes for existing classes.
module JSONable
module ClassMethods
attr_accessor :attributes
def attr_accessor *attrs
self.attributes = Array attrs
super
end
end
def self.included(base)
base.extend(ClassMethods)
end
def as_json options = {}
serialized = Hash.new
self.class.attributes.each do |attribute|
serialized[attribute] = self.public_send attribute
end
serialized
end
def to_json *a
as_json.to_json *a
end
end
class CustomClass
include JSONable
attr_accessor :b, :c
def initialize b: nil, c: nil
self.b, self.c = b, c
end
end
a = CustomClass.new(b: "q", c: 23)
puts JSON.pretty_generate a
{
"b": "q",
"c": 23
}
Looking for a solution on the same problem, i found this bug report on the Rails issue tracker. Besides it was closed, i suppose it still happens on the earlier versions. Hope it could help.
https://github.com/rails/rails/issues/576
Related
I am curious how this works. For example if I create a factory pattern based class where you can "register" classes for later use and then do something like
FactoryClass.register('YourClassName', [param, param, ...]);
FactoryClass.create('your_class_name').call_method_from_this_object
where 'class_name' is a key in a hash that maps to value: ClassName
is there anything like php reflection, where I can create an instance of a class based on a string name and pass in the arguments in? (in php the arguments would be an array of them that php then knows how what to do with)
So if we take a real world example:
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
# In the factory class, foo is then placed in a hash: {'foo' => 'Foo'}
# This step might not be required??
FactoryClass.create('Foo', ['hello'])
# Some where in your code:
FactoryClass.create('foo').get_something # => hello
Is this possible to do in ruby? I know everything is essentially an object, but I haven't seen any API or docs on creating class instances from string names like this and also passing in objects.
As for the hash above, thinking about it now I would probably have to do something like:
{'foo' => {'class' => 'Foo', 'params' => [param, param, ...]}}
This way when you call .create on the FactoryClass it would know, ok I can instantiate Foo with the associated params.
If I am way off base, please feel free to educate me.
Check out Module#const_get (retrieving a constant from a String) and Object#send (calling a method from a String).
Here is an answer that doesn't use eval.
PHP's Reflection is called Metaprogramming in Ruby, but they are quite different. Everything in Ruby is open and could be accessed.
Consider the following code:
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
#registered = { }
def register(reference_name, class_name, params=[])
#registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = #registered[reference_name]
Object.const_get(h[:class_name]).new(*(h[:params]))
end
register('foo', 'Foo', ['something'])
puts create('foo').get_something
You can use Object#const_get to get objects from strings. Object.const_get('Foo') will give you the object Foo.
However, you don't need to send class name as string. You can also pass around the class name as object and use that directly.
class Foo
attr_reader :something
def initialize(input)
#something = input
end
def get_something
return #something
end
end
#registered = { }
def register(reference_name, class_name, params=[])
#registered[reference_name] = { class_name: class_name, params: [params].flatten }
end
def create(reference_name)
h = #registered[reference_name]
h[:class_name].new(*(h[:params]))
end
register('foo', Foo, ['something else'])
puts create('foo').get_something
Actually one of the strong points in ruby is meta-programming. So this is really easy to do in ruby.
I am going to skip the registering part, and jump straight to the creation
A simple implementation would be this
class FactoryClass
def self.create(class_name, params)
klass = Object.const_get(class_name)
klass.new(*params)
end
end
and then you can just do:
FactoryClass.create('YourClassName', [param, param, ...]);
and this would be equivalent to calling
YourClassName.new(param, param, ...)
I have a rails application and a class I've wrote as a part of it (not an ActiveRecord or anything..). The data is stored in simple instance variables (string, integers, arrays...)
When I invoke to_json on an instance of it I get what I expect to. A JSON object, containing all instance variables as JSON objects too.
However, when I add include Enumerable to the class definition, the behavior of to_json changes and I get an empty object: "[]"
Any idea why is that? Does Enumerable somehow defined or undefines something that affects to_json?
Thanks!
So, what happens is:
Rails loads in ActiveSupport. ActiveSupport injects (monkey patches) these as_json methods into several classes and modules, including Enumerable:
module Enumerable
def as_json(options = nil) #:nodoc:
to_a.as_json(options)
end
end
You're probably returning nothing for the each method Enumerable requires you to have, so to_a returns [], and an empty array gets converted into the String "[]".
What you can do here, is, to bind your object into a non-enumerable inherited class, and use its .as_json method.
Like this:
class A
def as_json(*)
Object.instance_method(:as_json).bind(self).call
end
end
Demo:
➜ pry
require 'active_support/all'
=> true
class A
def initialize
#a = 1
end
end
=> nil
A.new.to_json
=> "{\"a\":1}"
class A
include Enumerable
def each
end
end
=> nil
A.new.to_json
=> "[]"
class A
def as_json(*)
Object.instance_method(:as_json).bind(self).call
end
end
=> nil
A.new.to_json
=> "{\"a\":1}"
This is my hash:
tempData = {"a" => 100, "here" => 200, "c" => "hello"}
I need to access the hash keys as a method like:
tempData.a #100
tempData.here # 200
You could just wrap up your hash in an OpenStruct:
require 'ostruct'
tempData = {"a" => 100, "here" => 200, "c" => "hello"}
os = OpenStruct.new tempData
os.a #=> 100
os.here #=> 200
If you really really wanted to, you could also monkey-patch the Hash class, but I'd advise against that:
class Hash
def method_missing(m, *args, &blk)
fetch(m) { fetch(m.to_s) { super } }
end
end
tempData = {"a" => 100, "here" => 200, "c" => "hello"}
tempData.a #=> 100
Update: In my personal extensions library I added a Hash#to_ostruct method. This will recursively convert a hash into an OpenStruct including all nested hashes.
There is another way to do this.
JSON.parse(tempData.to_json, object_class: OpenStruct)
that will give object
#<OpenStruct a=100, here=200, c="hello">
In this way nested hash also will be converted to OpenStruct Object
tempData = {a: { b: { c: 3}}, foo: 200, msg: 'test msg'}
obj = JSON.parse(tempData.to_json, object_class: OpenStruct)
Now we are able to call
obj.a.b.c # 3
obj.foo # 200
obj.msg # 'test msg'
Hope this will help someone.
Alternatively, if it’s just a small script it might be more convenient to just extend Hash itself
class Hash
def method_missing sym,*
fetch(sym){fetch(sym.to_s){super}}
end
end
method_missing is a magic method that is called whenever your code tries to call a method that does not exist. Ruby will intercept the failing call at run time and let you handle it so your program can recover gracefully. The implementation above tries to access the hash using the method name as a symbol, the using the method name as a string, and eventually fails with Ruby's built-in method missing error.
NB for a more complex script, where adding this behavior might break other third-party gems, you might alternatively use a module
and extend each instance
module H
def method_missing sym,*
fetch(sym){fetch(sym.to_s){super}}
end
end
the = { answer: 42 }
the.extend(H)
the.answer # => 42
and for greater convenience you can even propagate the module down to
nested hashes
module H
def method_missing sym,*
r = fetch(sym){fetch(sym.to_s){super}}
Hash === r ? r.extend(H) : r
end
end
the = { answer: { is: 42 } }
the.extend(H)
the.answer.is # => 42
If the hash is inside a module, you can define methods on that module (or class) dynamically using define_method. For example:
module Version
module_function
HASH = {
major: 1,
minor: 2,
patch: 3,
}
HASH.each do |name, value|
define_method(name) do
return value
end
end
end
This will define a Version module with major, minor, and patch methods that return 1, 2, and 3, respectively.
you can extend the Hash class in the following way.
class Hash
# return nil whenever the key doesn't exist
def method_missing(m, *opts)
if self.has_key?(m.to_s)
return self[m.to_s]
elsif self.has_key?(m.to_sym)
return self[m.to_sym]
end
return nil
# comment out above line and replace with line below if you want to return an error
# super
end
end
Is there a simple way to list the accessors/readers that have been set in a Ruby Class?
class Test
attr_reader :one, :two
def initialize
# Do something
end
def three
end
end
Test.new
=> [one,two]
What I'm really trying to do is to allow initialize to accept a Hash with any number of attributes in, but only commit the ones that have readers already defined. Something like:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
eval("##{opt} = \"#{val}\"")
end
end
Any other suggestions?
This is what I use (I call this idiom hash-init).
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| send("#{k}=", v) }
end
If you are on Ruby 1.9 you can do it even cleaner (send allows private methods):
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
end
This will raise a NoMethodError if you try to assign to foo and method "foo=" does not exist. If you want to do it clean (assign attrs for which writers exist) you should do a check
def initialize(object_attribute_hash = {})
object_attribute_hash.map do |(k, v)|
writer_m = "#{k}="
send(writer_m, v) if respond_to?(writer_m) }
end
end
however this might lead to situations where you feed your object wrong keys (say from a form) and instead of failing loudly it will just swallow them - painful debugging ahead. So in my book a NoMethodError is a better option (it signifies a contract violation).
If you just want a list of all writers (there is no way to do that for readers) you do
some_object.methods.grep(/\w=$/)
which is "get an array of method names and grep it for entries which end with a single equals sign after a word character".
If you do
eval("##{opt} = \"#{val}\"")
and val comes from a web form - congratulations, you just equipped your app with a wide-open exploit.
You could override attr_reader, attr_writer and attr_accessor to provide some kind of tracking mechanism for your class so you can have better reflection capability such as this.
For example:
class Class
alias_method :attr_reader_without_tracking, :attr_reader
def attr_reader(*names)
attr_readers.concat(names)
attr_reader_without_tracking(*names)
end
def attr_readers
#attr_readers ||= [ ]
end
alias_method :attr_writer_without_tracking, :attr_writer
def attr_writer(*names)
attr_writers.concat(names)
attr_writer_without_tracking(*names)
end
def attr_writers
#attr_writers ||= [ ]
end
alias_method :attr_accessor_without_tracking, :attr_accessor
def attr_accessor(*names)
attr_readers.concat(names)
attr_writers.concat(names)
attr_accessor_without_tracking(*names)
end
end
These can be demonstrated fairly simply:
class Foo
attr_reader :foo, :bar
attr_writer :baz
attr_accessor :foobar
end
puts "Readers: " + Foo.attr_readers.join(', ')
# => Readers: foo, bar, foobar
puts "Writers: " + Foo.attr_writers.join(', ')
# => Writers: baz, foobar
Try something like this:
class Test
attr_accessor :foo, :bar
def initialize(opts = {})
opts.each do |opt, val|
send("#{opt}=", val) if respond_to? "#{opt}="
end
end
end
test = Test.new(:foo => "a", :bar => "b", :baz => "c")
p test.foo # => nil
p test.bar # => nil
p test.baz # => undefined method `baz' for #<Test:0x1001729f0 #bar="b", #foo="a"> (NoMethodError)
This is basically what Rails does when you pass in a params hash to new. It will ignore all parameters it doesn't know about, and it will allow you to set things that aren't necessarily defined by attr_accessor, but still have an appropriate setter.
The only downside is that this really requires that you have a setter defined (versus just the accessor) which may not be what you're looking for.
Accessors are just ordinary methods that happen to access some piece of data. Here's code that will do roughly what you want. It checks if there's a method named for the hash key and sets an accompanying instance variable if so:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("##{opt}", val.to_s) if respond_to? opt
end
end
Note that this will get tripped up if a key has the same name as a method but that method isn't a simple instance variable access (e.g., {:object_id => 42}). But not all accessors will necessarily be defined by attr_accessor either, so there's not really a better way to tell. I also changed it to use instance_variable_set, which is so much more efficient and secure it's ridiculous.
There's no built-in way to get such a list. The attr_* functions essentially just add methods, create an instance variable, and nothing else. You could write wrappers for them to do what you want, but that might be overkill. Depending on your particular circumstances, you might be able to make use of Object#instance_variable_defined? and Module#public_method_defined?.
Also, avoid using eval when possible:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
instance_variable_set "##{opt}", val
end
end
You can look to see what methods are defined (with Object#methods), and from those identify the setters (the last character of those is =), but there's no 100% sure way to know that those methods weren't implemented in a non-obvious way that involves different instance variables.
Nevertheless Foo.new.methods.grep(/=$/) will give you a printable list of property setters. Or, since you have a hash already, you can try:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("##{opt}", val.to_s) if respond_to? "#{opt}="
end
end
How can I add an instance variable to a defined class at runtime, and later get and set its value from outside of the class?
I'm looking for a metaprogramming solution that allows me to modify the class instance at runtime instead of modifying the source code that originally defined the class. A few of the solutions explain how to declare instance variables in the class definitions, but that is not what I am asking about.
Ruby provides methods for this, instance_variable_get and instance_variable_set. (docs)
You can create and assign a new instance variables like this:
>> foo = Object.new
=> #<Object:0x2aaaaaacc400>
>> foo.instance_variable_set(:#bar, "baz")
=> "baz"
>> foo.inspect
=> #<Object:0x2aaaaaacc400 #bar=\"baz\">
You can use attribute accessors:
class Array
attr_accessor :var
end
Now you can access it via:
array = []
array.var = 123
puts array.var
Note that you can also use attr_reader or attr_writer to define just getters or setters or you can define them manually as such:
class Array
attr_reader :getter_only_method
attr_writer :setter_only_method
# Manual definitions equivalent to using attr_reader/writer/accessor
def var
#var
end
def var=(value)
#var = value
end
end
You can also use singleton methods if you just want it defined on a single instance:
array = []
def array.var
#var
end
def array.var=(value)
#var = value
end
array.var = 123
puts array.var
FYI, in response to the comment on this answer, the singleton method works fine, and the following is proof:
irb(main):001:0> class A
irb(main):002:1> attr_accessor :b
irb(main):003:1> end
=> nil
irb(main):004:0> a = A.new
=> #<A:0x7fbb4b0efe58>
irb(main):005:0> a.b = 1
=> 1
irb(main):006:0> a.b
=> 1
irb(main):007:0> def a.setit=(value)
irb(main):008:1> #b = value
irb(main):009:1> end
=> nil
irb(main):010:0> a.setit = 2
=> 2
irb(main):011:0> a.b
=> 2
irb(main):012:0>
As you can see, the singleton method setit will set the same field, #b, as the one defined using the attr_accessor... so a singleton method is a perfectly valid approach to this question.
#Readonly
If your usage of "class MyObject" is a usage of an open class, then please note you are redefining the initialize method.
In Ruby, there is no such thing as overloading... only overriding, or redefinition... in other words there can only be 1 instance of any given method, so if you redefine it, it is redefined... and the initialize method is no different (even though it is what the new method of Class objects use).
Thus, never redefine an existing method without aliasing it first... at least if you want access to the original definition. And redefining the initialize method of an unknown class may be quite risky.
At any rate, I think I have a much simpler solution for you, which uses the actual metaclass to define singleton methods:
m = MyObject.new
metaclass = class << m; self; end
metaclass.send :attr_accessor, :first, :second
m.first = "first"
m.second = "second"
puts m.first, m.second
You can use both the metaclass and open classes to get even trickier and do something like:
class MyObject
def metaclass
class << self
self
end
end
def define_attributes(hash)
hash.each_pair { |key, value|
metaclass.send :attr_accessor, key
send "#{key}=".to_sym, value
}
end
end
m = MyObject.new
m.define_attributes({ :first => "first", :second => "second" })
The above is basically exposing the metaclass via the "metaclass" method, then using it in define_attributes to dynamically define a bunch of attributes with attr_accessor, and then invoking the attribute setter afterwards with the associated value in the hash.
With Ruby you can get creative and do the same thing many different ways ;-)
FYI, in case you didn't know, using the metaclass as I have done means you are only acting on the given instance of the object. Thus, invoking define_attributes will only define those attributes for that particular instance.
Example:
m1 = MyObject.new
m2 = MyObject.new
m1.define_attributes({:a => 123, :b => 321})
m2.define_attributes({:c => "abc", :d => "zxy"})
puts m1.a, m1.b, m2.c, m2.d # this will work
m1.c = 5 # this will fail because c= is not defined on m1!
m2.a = 5 # this will fail because a= is not defined on m2!
Mike Stone's answer is already quite comprehensive, but I'd like to add a little detail.
You can modify your class at any moment, even after some instance have been created, and get the results you desire. You can try it out in your console:
s1 = 'string 1'
s2 = 'string 2'
class String
attr_accessor :my_var
end
s1.my_var = 'comment #1'
s2.my_var = 'comment 2'
puts s1.my_var, s2.my_var
The other solutions will work perfectly too, but here is an example using define_method, if you are hell bent on not using open classes... it will define the "var" variable for the array class... but note that it is EQUIVALENT to using an open class... the benefit is you can do it for an unknown class (so any object's class, rather than opening a specific class)... also define_method will work inside a method, whereas you cannot open a class within a method.
array = []
array.class.send(:define_method, :var) { #var }
array.class.send(:define_method, :var=) { |value| #var = value }
And here is an example of it's use... note that array2, a DIFFERENT array also has the methods, so if this is not what you want, you probably want singleton methods which I explained in another post.
irb(main):001:0> array = []
=> []
irb(main):002:0> array.class.send(:define_method, :var) { #var }
=> #<Proc:0x00007f289ccb62b0#(irb):2>
irb(main):003:0> array.class.send(:define_method, :var=) { |value| #var = value }
=> #<Proc:0x00007f289cc9fa88#(irb):3>
irb(main):004:0> array.var = 123
=> 123
irb(main):005:0> array.var
=> 123
irb(main):006:0> array2 = []
=> []
irb(main):007:0> array2.var = 321
=> 321
irb(main):008:0> array2.var
=> 321
irb(main):009:0> array.var
=> 123
Readonly, in response to your edit:
Edit: It looks like I need to clarify
that I'm looking for a metaprogramming
solution that allows me to modify the
class instance at runtime instead of
modifying the source code that
originally defined the class. A few of
the solutions explain how to declare
instance variables in the class
definitions, but that is not what I am
asking about. Sorry for the confusion.
I think you don't quite understand the concept of "open classes", which means you can open up a class at any time. For example:
class A
def hello
print "hello "
end
end
class A
def world
puts "world!"
end
end
a = A.new
a.hello
a.world
The above is perfectly valid Ruby code, and the 2 class definitions can be spread across multiple Ruby files. You could use the "define_method" method in the Module object to define a new method on a class instance, but it is equivalent to using open classes.
"Open classes" in Ruby means you can redefine ANY class at ANY point in time... which means add new methods, redefine existing methods, or whatever you want really. It sounds like the "open class" solution really is what you are looking for...
I wrote a gem for this some time ago. It's called "Flexible" and not available via rubygems, but was available via github until yesterday. I deleted it because it was useless for me.
You can do
class Foo
include Flexible
end
f = Foo.new
f.bar = 1
with it without getting any error. So you can set and get instance variables from an object on the fly.
If you are interessted... I could upload the source code to github again. It needs some modification to enable
f.bar?
#=> true
as method for asking the object if a instance variable "bar" is defined or not, but anything else is running.
Kind regards, musicmatze
It looks like all of the previous answers assume that you know what the name of the class that you want to tweak is when you are writing your code. Well, that isn't always true (at least, not for me). I might be iterating over a pile of classes that I want to bestow some variable on (say, to hold some metadata or something). In that case something like this will do the job,
# example classes that we want to tweak
class Foo;end
class Bar;end
klasses = [Foo, Bar]
# iterating over a collection of klasses
klasses.each do |klass|
# #class_eval gets it done
klass.class_eval do
attr_accessor :baz
end
end
# it works
f = Foo.new
f.baz # => nil
f.baz = 'it works' # => "it works"
b = Bar.new
b.baz # => nil
b.baz = 'it still works' # => "it still works"