How to create a deep copy of an object in Ruby? - ruby-on-rails

I did some searching found some different methods and posts about creating a deep copy operator.
Is there a quick and easy (built-in) way to deep copy objects in Ruby? The fields are not arrays or hashes.
Working in Ruby 1.9.2.

Deep copy isn't built into vanilla Ruby, but you can hack it by marshalling and unmarshalling the object:
Marshal.load(Marshal.dump(#object))
This isn't perfect though, and won't work for all objects. A more robust method:
class Object
def deep_clone
return #deep_cloning_obj if #deep_cloning
#deep_cloning_obj = clone
#deep_cloning_obj.instance_variables.each do |var|
val = #deep_cloning_obj.instance_variable_get(var)
begin
#deep_cloning = true
val = val.deep_clone
rescue TypeError
next
ensure
#deep_cloning = false
end
#deep_cloning_obj.instance_variable_set(var, val)
end
deep_cloning_obj = #deep_cloning_obj
#deep_cloning_obj = nil
deep_cloning_obj
end
end
Source:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/43424

I've created a native implementation to perform deep clones of ruby objects.
It's approximately 6 to 7 times faster than the Marshal approach.
https://github.com/balmma/ruby-deepclone
Note that this project is not maintained anymore (last commit in 2017, there are reported issues)

Rails has a recursive method named deep_dup that will return a deep copy of an object and, on the contrary of dup and clone, works even on composite objects (array/hash of arrays/hashes).
It's as easy as:
def deep_dup
map { |it| it.deep_dup }
end

There is a native implementation to perform deep clones of ruby objects: ruby_deep_clone
Install it with gem:
gem install ruby_deep_clone
Example usage:
require "deep_clone"
object = SomeComplexClass.new()
cloned_object = DeepClone.clone(object)
It's approximately 6 to 7 times faster than the Marshal approach and event works with frozen objects.
Note that this project is not maintained anymore (last commit in 2017, there are reported issues)

You can use duplicate gem for this.
It's a small pure ruby gem that able to recursively duplicate object
It will duplicate it's object references too to the new duplication.
require 'duplicate'
duplicate('target object')
https://rubygems.org/gems/duplicate
https://github.com/adamluzsi/duplicate.rb

Automatic deep clone is not always what you want. Often you need to define a selected few attributes to deep clone. A flexible way to do this is to implement the initialize_copy, initialize_dup and initialize_clone methods.
If you have a class:
class Foo
attr_accessor :a, :b
end
and you only want to only deep clone :b, you override the initialize_* method:
class Foo
attr_accessor :a, :b
def initialize_dup(source)
#b = #b.dup
super
end
end
Of course if you want #b to also deep clone some of its own attributes, you do the same in b's class.
Rails does this (see https://github.com/rails/rails/blob/0951306ca5edbaec10edf3440d5ba11062a4f2e5/activemodel/lib/active_model/errors.rb#L78)
For more complete explanation, I learned it here from this post https://aaronlasseigne.com/2014/07/20/know-ruby-clone-and-dup/

I would suggest you use the ActiveSupport gem which adds a lot of sugar to your native Ruby core, not just a deep clone method.
You can look into the documentation for more information regarding the methods that have been added.

You really don't need a Gem for this. This couldn't be much simpler than this, which is not worth the overhead of a Gem!
def deep_clone(obj)
obj.clone.tap do |new_obj|
new_obj.each do |key, val|
new_obj[key] = deep_clone(val) if val.is_a?(Hash)
end
end
end

Also check out deep_dive. This allows you to do controlled deep copies of your object graphs.
https://rubygems.org/gems/deep_dive

Related

When and how is it useful to dynamically add a method to a Ruby object?

I have been trying to learn Ruby from 'The Well-grounded Rubyist' and I came across the idea of adding methods to an object at run-time:
obj = Object.new
obj.respond_to? "hello" # Returns false
def obj.hello
puts "something"
end
obj.respond_to? "hello" # Returns true
obj.hello() # Output is "something"
I have a background in Python and Java, and I cannot imagine any way for me to use this new idea. So, how is this useful? How does it fit into the spirit of object-oriented programming? Is it expensive to do this at run-time?
There's always a long list of things you can do in any language but shouldn't do without a good reason and extending a single object is certainly high on that list.
Normally you wouldn't define individual methods, but you might include a bunch of them:
module Extensions
def is_special?
true
end
end
obj = Object.new
obj.send(:extend, Extensions)
obj.is_special?
# => true
ActiveRecord from Rails does this to dynamically create methods for models based on whatever the schema is at the time the Rails instance is launched, so each column gets an associated method. This sort of dynamic programming can be used to make the code adapt seamlessly to a changing environment.
There's a lot of cases where you'll want to spell this out explicitly so your methods are well documented, but for cases where it doesn't matter and responding dynamically is better than maintaining two things, like schema and the associated methods in your code, then it could be the best option.

How can I list all Ruby classes instantiated?

I'd like to list all classes instantiated during one Ruby on Rails API query.
Is there a benchmarking tool or a profiler I could use?
Check out Object_space#each_object:
ObjectSpace.each_object.to_a
As an addition to ndn's answer:
ObjectSpace#each_object allows you to iterate through all instances of a specific class. For example:
ObjectSpace.each_object(String) do |object|
p object
end
ObjectSpace#count_objects shows number of instances of each class.
p ObjectSpace.count_objects
# Result:
# {:TOTAL=>30163,
# :FREE=>1007,
# :T_OBJECT=>39,
# :T_CLASS=>534,
# :T_MODULE=>24,
Check out this wonderful repository for more useful ruby tricks.

Create encode and decode methods in rails?

I'm using the base62 gem for obfuscation/shortening ids. So I have things like:
552.base62_encode
=> "8u"
"8u".base62_decode
=> 552
I'd like to alias these in an initializer so they're cleaner, like so:
class Fixnum
def encode
base62_encode
end
end
class String
def decode
base62_decode
end
end
Are there any issues with using the words encode and decode directly? Will this cause problems with any interdependencies, other gems, etc, or is this safe to do?
Well, since the String#encode method is defined by ruby, yes, it will cause problems.
As a general note, you should run away from monkey patching as fast as you can.
It will only make your applications harder to comprehend for new developers, and harder to maintain.

What is the best way to obfuscate numerical IDs in an application

Given I've got a site where most of the resources have numerical IDs (i.e. user.id question.id etc.) but that like the Germans looking back on WWII I'd rather not reveal these to the observers, what's the best way to obfuscate them?
I presume the method is going to involve the .to_param and then some symmetric encryption algorithm but I'm not sure what's the most efficient encryption to do and how it'll impact lookup times in the DB etc.
Any advice from the road trodden would be much appreciated.
I published a Rails plugin that does this called obfuscate_id. I didn't need it to be secure, but just to make the id in the url non-obvious to the casual user. I also wanted it to look cleaner than a long hash.
It also has the advantage of needing no migrations or database changes. It's pretty simple.
Just add the gem to your Gemfile:
gem 'obfuscate_id'
And add call the obfuscate id in your model:
class Post < ActiveRecord::Base
obfuscate_id
end
This will create urls like this:
# post 7000
http://example.com/posts/5270192353
# post 7001
http://example.com/posts/7107163820
# post 7002
http://example.com/posts/3296163828
You also don't need to look up the records in any special way, ActiveRecord find just works.
Post.find(params[:id])
More information here:
https://github.com/namick/obfuscate_id
I usually use a salted Hash and store it in the DB in an indexed field. It depends on the level of security you expect, but I use one salt for all.
This method makes the creation a bit more expensive, because you are going to have an INSERT and an UPDATE, but your lookups will be quite fast.
Pseudo code:
class MyModel << ActiveRecord::Base
MY_SALT = 'some secret string'
after_create :generate_hashed_id
def to_param
self.hashed_id
end
def generate_hashed_id
self.update_attributes(:hashed_id => Digest::SHA1.hexdigest("--#{MY_SALT}--#{self.id}--"))
end
end
Now you can look up the record with MyModel.find_by_hashed_id(params[:id]) without any performance repercussions.
Here's a solution. It's the same concept as Wukerplank's answer, but there's a couple of important differences.
1) There's no need to insert the record then update it. Just set the uuid before inserting by using the before_create callback. Also note the set_uuid callback is private.
2) There's a handy library called SecureRandom. Use it! I like to use uuid's, but SecureRandom can generate other types of random numbers as well.
3) To find the record use User.find_by_uuid!(params[:id]). Notice the "!". That will raise an error if the record is not found just like User.find(params[:id]) would.
class User
before_create :set_uuid
def to_param
uuid
end
private
def set_uuid
self.uuid = SecureRandom.uuid
end
end
Hashids is a great cross-platform option.
You can try using this gem,
https://github.com/wbasmayor/masked_id
it obfuscates your id and at the same time giving each model it's own obfuscated code so all no. 1 id won't have the same hash. Also, it does not override anything on the rails side, it just provides new method so it doesn't mess up your rails if your also extending them.
Faced with a similar problem, I created a gem to handle the obfuscation of Model ids using Blowfish. This allows the creation of nice 11 character obfuscated ids on the fly. The caveat is, the id must be within 99,999,999, e.g. a max length of 8.
https://github.com/mguymon/obfuscate
To use with Rails, create an initializer in config/initializers with:
require 'obfuscate/obfuscatable'
Obfuscate.setup do |config|
config.salt = "A weak salt ..."
end
Now add to models that you want to be Obfuscatable:
class Message < ActiveRecord::Base
obfuscatable # a hash of config overrides can be passed.
end
To get the 11 character obfuscated_id, which uses the Blowfish single block encryption:
message = Message.find(1)
obfuscated = message.obfuscated_id # "NuwhZTtHnko"
clarified = message.clarify_id( obfuscated ) # "1"
Message.find_by_obfuscated_id( obfuscated )
Or obfuscate a block of text using Blowfish string encryption, allowing longer blocks of text to be obfuscated:
obfuscated = message.obfuscate( "if you use your imagination, this is a long block of text" ) # "GoxjVCCuBQgaLvttm7mXNEN9U6A_xxBjM3CYWBrsWs640PVXmkuypo7S8rBHEv_z1jP3hhFqQzlI9L1s2DTQ6FYZwfop-xlA"
clarified = message.clarify( obfuscated ) # "if you use your imagination, this is a long block of text"

Ancestry gem: return only last child in threads

The ancestry gem has a lot of methods to navigate the tree structure. You can do Model.roots to show all root elements etc. How do opposite? - return newest child for each tree structure.
I thought about adding an extra column to my model (latest/boolean) and then do some logic with after save filters etc. However this feels a bit clumsy. :/
Best regards.
Asbjørn Morell
Maybe you can hack something together with the Class#inherited hook, like updating an attribute of the parent model on creation of the new subclass:
http://www.ruby-doc.org/core/classes/Class.html#M000177
Old question, still relevant.
Found myself a solution (bit clumsy as well) but I think it works fairly well.
Extend the Array class with a method like this:
class Array
def ancestry_last_child
last_child = self.map { |a| [a[:id], a[:ancestry], a[:updated_at]] }.sort_by { |id, anc, dat| [anc.split('/').length, dat] }.last
self.find { |a| a[:id] == last_child[0] }
end
end
After that just use it (with preceding validation) like this:
account = Account.find([id])
if account.has_children?
last_child = account.descendants.ancestry_last_child
end
if you have doubts about extending ruby/rails core classes this will help
::EDIT::
previous solution didn't work in full. This solution requires an updated_at and id column

Resources