How to Stringize/Serialize Ruby Code

How do you stringize/serialize Ruby code?

Use Ruby2Ruby

def save_for_later(&block)
return nil unless block_given?

c = Class.new
c.class_eval do
define_method :serializable, &block
end
s = Ruby2Ruby.translate(c, :serializable)
s.sub(/^def \S+\(([^\)]*)\)/, 'lambda { |\1|').sub(/end$/, '}')
end

x = 40
s = save_for_later { |y| x + y }
# => "lambda { |y|\n (x + y)\n}"
g = eval(s)
# => #<Proc:0x4037bb2c@(eval):1>
g.call(2)
# => 42

This is great, but it does not close over free variables (like x) and serialize them along with the lambda.

To serialize variables also, you can iterate over local_variables and serialize them as well. The problem, though, is that local_variables from within save_for_later accesses only c and s in the code above -- i.e. variables local to the serialization code, not the caller. So unfortunately, we must push the grabbing of local variables and their values to the caller.

Maybe this is a good thing, though, because in general, finding all free variables in a piece of Ruby code is undecidable. Plus, ideally we would also save global_variables and any loaded classes and their overridden methods. This seems impractical.

Using this simple approach, you get the following:

def save_for_later(local_vars, &block)
return nil unless block_given?

c = Class.new
c.class_eval do
define_method :serializable, &block
end
s = Ruby2Ruby.translate(c, :serializable)
locals = local_vars.map { |var,val| "#{var} = #{val.inspect}; " }.join
s.sub(/^def \S+\(([^\)]*)\)/, 'lambda { |\1| ' + locals).sub(/end$/, '}')
end

x = 40
s = save_for_later(local_variables.map{ |v| [v,eval(v)] }) { |y| x + y }
# => "lambda { |y| _ = 40; x = 40;\n (x + y)\n}"

# In a separate run of Ruby, where x is not defined...
g = eval("lambda { |y| _ = 40; x = 40;\n (x + y)\n}")
# => #<Proc:0xb7cfe9c0@(eval):1>
g.call(2)
# => 42

# Changing x does not affect it.
x = 7
g.call(3)
# => 43

How do I marshal a lambda (Proc) in Ruby?

You cannot marshal a Lambda or Proc. This is because both of them are considered closures, which means they close around the memory on which they were defined and can reference it. (In order to marshal them you'd have to Marshal all of the memory they could access at the time they were created.)

As Gaius pointed out though, you can use ruby2ruby to get a hold of the string of the program. That is, you can marshal the string that represents the ruby code and then reevaluate it later.

Convert symbols to string when serializing with Oj.dump

While I was writing the question, I was able to find the answer. Since I cannot find any other answers on StackOverflow relating to this issue (specifically with regards to the Oj gem), I will keep this post in the hopes it will help others in my situation.

According to this previously discussed issue on GitHub, setting the option mode to :compat will indeed convert the symbols to strings. So my render line now looks like this:

render json: Oj.dump(example_hash, mode: :compat)

According to the Oj documentation for default_options, :compat mode is defined as follows:

...compatible with other systems. It will serialize any Object but will
check to see if the Object implements a to_hash() or to_json() method.
If either exists that method is used for serializing the Object. The
to_hash() is more flexible and produces more consistent output so it
has a preference over the to_json() method. If neither the to_json()
or to_hash() methods exist then the Oj internal Object variable
encoding is used.

So if I am interpreting that correctly, it seems this solution works because it is ultimately using the to_json method of the Hash class.

I am uncertain whether I have affected performance (either positively or negatively), but at least it saves me from having to manually invoke with_indifferent_access or to_json in the case of an array.

Update

In regards to performance, cmwright did some benchmarking, and came up with these results:

Rehearsal ----------------------------------------------
json 13.990000 0.250000 14.240000 ( 14.257051)
oj default 3.260000 0.230000 3.490000 ( 3.491028)
oj compat 3.360000 0.240000 3.600000 ( 3.593109)
------------------------------------ total: 21.330000sec

user system total real
json 13.740000 0.240000 13.980000 ( 13.992641)
oj default 3.020000 0.230000 3.250000 ( 3.248077)
oj compat 3.060000 0.230000 3.290000 ( 3.286443)

Seems like the compat option is at least on par with the default Oj options, and significantly more efficient than plain 'ol to_json.

This is the gist containing the benchmark code.

How do you access the symbol table in Ruby?

I think he comes from a perl background , and that he would like to obtain all the variables defined in a script and serialize them . This way , when he'll load the file , he'll get them back .
I'm still searching about how to get a list of the variables , but serialization will be made using Marshal.dump and reading them back will be made with Marshal.load . I'll edit the post once I find out how to get a list of all defined variables .

EDIT : found it!

You can get a list of all variables by calling these methods :


local_variables
global_variables

And if you haven't already got your serialization code , I would suggest something like this:

  • create a class or a Struct instance that holds a variable name and the value of the variable and add them in an array :


local_variables.each {|var| my_array << MyVarObject.new(var,eval(var)) } # eval is used to get the value of the variable

and then serialize the array :


data = Marshal.dump(my_array)
File.open("myfile.ser","w") do |file|
file.puts data
end

Ruby on Rails JSON serialization

Read up the documentation on Rails to_json here.

The option
ActiveRecord::Base.include_root_in_json
controls the top-level behavior of
to_json. In a new Rails application,
it is set to true in
initializers/new_rails_defaults.rb.
When it is true, to_json will emit a
single root node named after the
object’s type.

So adding to your environment/application.rb

config.active_record.include_root_in_json = true

should solve your issues.

Serialize an array of models using active_model_serializers

I have a controller that I need to specify the serializer in, due to wanting different attributes from the default serializer.

In Controller:

  def index
search = User.ransack(search_params)
render json: search.result, each_serializer: MembershipRenewalSerializer::MemberSerializer
end

So, just to get things working, what happens if you specify the each_serializer option?

Edits:

Outside Controller:

ActiveModel::SerializableResource.new(
User.first(2),
each_serializer: MembershipRenewalSerializer::MemberSerializer
).to_json

Note, that without specifying each_serializer, SerializableResource would use the UserSerializer.

Edit #2,

It looks like there is something weird happening with the @admins data.

Try converting to an array:

ActiveModel::SerializableResource.new(@admins.to_a).to_json 

Edit #3

To paginate your array, try the following:

@search = Admin.search(params[:q])
@results = @search.result(:distinct => true).to_a
@admins = Kaminari.paginate_array(@results).page(params[:page]).per(10)

How to serialize GET params with (jQuery Style) Nested Objects

Use $httpParamSerializerJQLike:

var myUrl = '/clients.json'
var myParams = {c: {year: ["2015", "2016"]}};

$http({
url: myUrl,
method: 'GET',
params: myParams,
paramSerializer: '$httpParamSerializerJQLike'
});

The $httpParamSerializerJQLike service is the alternative $http params serializer that follows jQuery's param() method logic.

JSON:   {"c":{"year":[2014,2015]}}
Encode: c%5Byear%5D%5B%5D=2014&c%5Byear%5D%5B%5D=2015
Decode: c[year][]=2014&c[year][]=2015

The DEMO on JSFiddle


For some reason when I run:

var myParams = {c: {year: ["","2015","2016"]}};
var myParamsString = JSON.stringify(myParams);
var myParamsSerial = $httpParamSerializerJQLike(myParamsSerial);

the myParamsSerial comes back as blank. Any ideas?

Don't stringify the object:

 var myParams = {c: {year: ["","2015","2016"]}};
//DONT stringify
//var myParamsString = JSON.stringify(myParams);
//var myParamsSerial = $httpParamSerializerJQLike(myParamsSerial);
//USE directly
var myParamsSerial = $httpParamSerializerJQLike(myParams);

The params serializer serializes a JavaScript object, not a JSON string.



Related Topics



Leave a reply



Submit