How to use Resolv::DNS::Resource::Generic - ruby

I would like to better understand how Resolv::DNS handles records that are not directly supported. These records are represented by the Resolv::DNS::Resource::Generic class, but I could not find documentation about how to get the data out of this record.
Specifically, my zone will contain SSHFP and TLSA records, and I need a way to get to that data.

Through reverse engineering, I found the answer - documenting it here for others to see.
Please note that this involves undocumented features of the Resolv::DNS module, and the implementation may change over time.
Resource Records that the Resolv::DNS module does not understand are represented not through the Generic class, but rather through a subclass whose name represents the type and class of the DNS response - for instance, an SSHFP record (type 44) will be represented as Resolv::DNS::Resource::Generic::Type44_Class1
The object will contain a method "data" that gives you access to the RDATA of the record, in plain binary format.
Thus, to access an SSHFP record, here is how to get it:
def handle_sshfp(rr) do
# the RDATA is a string but contains binary data
data = rr.data.bytes
algo = data[0].to_s
fptype = data[1].to_s
fp = data[2..-1].to_s
hex = fp.map{|b| b.to_s(16).rr.rjust(2,'0') }.join(':')
puts "The SSHFP record is: #{fptype} #{algo} #{hex}"
end
Resolv::DNS.open do |dns|
all_records = dns.getresources('myfqdn.example.com', Resolv::DNS::Resource::IN::ANY ) rescue nil
all_records.each do |rr|
if rr.is_a? Resolv::DNS::Resource::Generic then
classname = rr.class.name.split('::').last
handle_sshfp(rr) if classname == "Type44_Class1"
end
end
end

Related

What's the correct way to read an array from a csv field in Ruby?

I am trying to save some class objects to a csv file, everything works fine. I can save and read back from the csv file, there is only a 'minor' problem with an attribute that is an Array of Strings.
When I save it to the file it appears like this: "[""Dan Brown""]"
CSV.open('documents.csv', "w") do |csv|
csv << %w[ISBN Titre Auteurs Type Disponibilité]
#docs.each { |doc|
csv << [doc.isbn, doc.titre, doc.auteurs, doc.type, doc.empruntable ? "Disponible" : "Emprunté"]
}
end
And when I try to extract the data from the file I end up with something like this: ["[\"Dan Brown\"]"].
table = CSV.parse(File.read("documents.csv"), headers: true)
table.each do |row|
doc = Document.new(row['Titre'], row['ISBN'], row['Type'])
doc.auteurs << row['Auteurs'] #This the array where there is a 'problem'
if row['Disponibilité'] == "Disponible"
doc.empruntable = true
else
doc.empruntable = false
end
#docs.push(doc) #this an array where I save my objects
end
I tried many things to solve this but without any luck. I would be thankful if you can help me find a solution.
Since a CSV file, by it's nature, contains in its fields only strings, not arrays or other data types, the CSV class is applying the to_s method of the objects to turn them into a string before putting them into the CSV.
When you later read them back, you just get this - the string representation of what once had been your array. The only one who knows that 'Auteurs' should end up as an array of strings, is the application, i.e. you.
Hence on reading the CSV, after having extracted the autheurs string, you need to convert it manually back to an Array, because there is no automatic "inverse method" to reverse the to_s.
A cheap, but dangerous way to do it, is to use eval, which indeed would reconstruct your array. However, you need to be sure that nobody had a chance to fiddle manually with the CSV data, because an eval allows sneaking in arbitrary code.
A safer way would be to either write your own conversion function to and from String representation, or use a format such as YAML or JSON for representing the Array as String, instead of using to_s.

rails string substitution or similar solution in controller

I'm building a site with users in all 50 states. We need to display information for each user that is specific to their situation, e.g., the number of events they completed in that state. Each state's view (a partial) displays state-specific information and, therefore, relies upon state-specific calculations in a state-specific model. We'd like to do something similar to this:
##{user.state} = #{user.state.capitalize}.new(current_user)
in the users_controller instead of
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
.... [and the remaining 49 states]
#wisconsin = Wisconsin.new(current_user) if (#user.state == 'wisconsin')
to trigger the Illinois.rb model and, in turn, drive the view defined in the users_controller by
def user_state_view
#user = current_user
#events = Event.all
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
end
I'm struggling to find a better way to do this / refactor it. Thanks!
I would avoid dynamically defining instance variables if you can help it. It can be done with instance_variable_set but it's unnecessary. There's no reason you need to define the variable as #illinois instead of just #user_state or something like that. Here is one way to do it.
First make a static list of states:
def states
%{wisconsin arkansas new_york etc}
end
then make a dictionary which maps those states to their classes:
def state_classes
states.reduce({}) do |memo, state|
memo[state] = state.camelize.constantize
memo
end
end
# = { 'illinois' => Illinois, 'wisconsin' => Wisconsin, 'new_york' => NewYork, etc }
It's important that you hard-code a list of state identifiers somewhere, because it's not a good practice to pass arbitrary values to contantize.
Then instantiating the correct class is a breeze:
#user_state = state_classes[#user.state].new(current_user)
there are definitely other ways to do this (for example, it could be added on the model layer instead)

How to use polymorphism to remove a switch statement which compares strings?

I am new to Ruby, so let me describe the context of my problem first:
I have a json as input which has the following key / value pair:
{
"service": "update"
}
The value has many different values for example: insert,delete etc.
Next there is a method x which handles the different requests:
def x(input)
case input[:service]
services = GenericService.new
when "update"
result = services.service(UpdateService.new,input)
when "insert"
result = services.service(InsertService.new,input)
when "delete"
result = services.service(DeleteService.new,input)
....
....
else
raise "Unknown service"
end
puts JSON.pretty_generate(result)
end
What is bothering me is that I still need to use a switch statement to check the String values (reminds me of 'instance of' ugh..). Is there a cleaner way (not need to use a switch)?
Finally I tried to search for an answer to my question and did not succeed, if however I missed it feel free to comment the related question.
Update: I was thinking to maybe cast the string to the related class name as follows: How do I create a class instance from a string name in ruby? and then call result = services.services(x.constantize.new,input) , then the class names ofcourse needs to match the input of the json.
You can try something like:
def x(input)
service_class_name = "#{input[:service].capitalize}Service"
service_class = Kernel.const_get(service_class_name)
service_class.new(input).process
end
In addition you might want to check if this is a valid Service class name at all.
I don't understand why you want to pass the service to GenericService this seems strange. let the service do it's job.
If you're trying to instatiate a class by it's name you're actually speaking about Reflection rather than Polymorphism.
In Ruby you can achieve this in this way:
byName = Object.const_get('YourClassName')
or if you are in a Rails app
byName= 'YourClassName'.constantize
Hope this helps
Just first thoughts, but you can do:
eval(services.service("#{input[:service].capitalize}Service.new, #{input})") if valid_service? input[:service]
def valid_service?
w%(delete update insert).include? input[:service]
end
As folks will no doubt shout, eval needs to be used with alot of care

Create an object if one is not found

How do I create an object if one is not found? This is the query I was running:
#event_object = #event_entry.event_objects.find_all_by_plantype('dog')
and I was trying this:
#event_object = EventObject.new unless #event_entry.event_objects.find_all_by_plantype('dog')
but that does not seem to work. I know I'm missing something very simple like normal :( Thanks for any help!!! :)
find_all style methods return an array of matching records. That is an empty array if no matching records are found. And an empty is truthy. Which means:
arr = []
if arr
puts 'arr is considered turthy!' # this line will execute
end
Also, the dynamic finder methods (like find_by_whatever) are officially depreacted So you shouldn't be using them.
You probably want something more like:
#event_object = #event_entry.event_objects.where(plantype: 'dog').first || EventObject.new
But you can also configure the event object better, since you obviously want it to belong to #event_entry.
#event_object = #event_entry.event_objects.where(plantype: 'dog').first
#event_object ||= #event_entry.event_objects.build(plantype: dog)
In this last example, we try to find an existing object by getting an array of matching records and asking for the first item. If there are no items, #event_object will be nil.
Then we use the ||= operator that says "assign the value on the right if this is currently set to a falsy value". And nil is falsy. So if it's nil we can build the object form the association it should belong to. And we can preset it's attributes while we are at it.
Why not use built in query methods like find_or_create_by or find_or_initialize_by
#event_object = #event_entry.event_objects.find_or_create_by(plantype:'dog')
This will find an #event_entry.event_object with plantype = 'dog' if one does not exist it will then create one instead.
find_or_initialize_by is probably more what you want as it will leave #event_object in an unsaved state with just the association and plantype set
#event_object = #event_entry.event_objects.find_or_initialize_by(plantype:'dog')
This assumes you are looking for a single event_object as it will return the first one it finds with plantype = 'dog'. If more than 1 event_object can have the plantype ='dog' within the #event_entry scope then this might not be the best solution but it seems to fit with your description.

Sinatra can't convert Symbol into Integer when making MongoDB query

This is a sort of followup to my other MongoDB question about the torrent indexer.
I'm making an open source torrent indexer (like a mini TPB, in essence), and offer both SQLite and MongoDB for backend, currently.
However, I'm having trouble with the MongoDB part of it. In Sinatra, I get when trying to upload a torrent, or search for one.
In uploading, one needs to tag the torrent — and it fails here. The code for adding tags is as follows:
def add_tag(tag)
if $sqlite
unless tag_exists? tag
$db.execute("insert into #{$tag_table} values ( ? )", tag)
end
id = $db.execute("select oid from #{$tag_table} where tag = ?", tag)
return id[0]
elsif $mongo
unless tag_exists? tag
$tag.insert({:tag => tag})
end
return $tag.find({:tag => tag})[:_id] #this is the line it presumably crashes on
end
end
It reaches line 105 (noted above), and then fails. What's going on? Also, as an FYI this might turn into a few other questions as solutions come in.
Thanks!
EDIT
So instead of returning the tag result with [:_id], I changed the block inside the elsif to:
id = $tag.find({:tag => tag})
puts id.inspect
return id
and still get an error. You can see a demo at http://torrent.hypeno.de and the source at http://github.com/tekknolagi/indexer/
Given that you are doing an insert(), the easiest way to get the id is:
id = $tag.insert({:tag => tag})
id will be a BSON::ObjectId, so you can use appropriate methods depending on the return value you want:
return id # BSON::ObjectId('5017cace1d5710170b000001')
return id.to_s # "5017cace1d5710170b000001"
In your original question you are trying to use the Collection.find() method. This returns a Mongo::Cursor, but you are trying to reference the cursor as a document. You need to iterate over the cursor using each or next, eg:
cursor = $tag.find_one({:tag => tag})
return cursor.next['_id'];
If you want a single document, you should be using Collection.find_one().
For example, you can find and return the _id using:
return $tag.find_one({:tag => tag})['_id']
I think the problem here is [:_id]. I dont know much about Mongo but `$tag.find({:tag => tag}) is probably retutning an array and passing a symbol to the [] array operator is not defined.

Resources