How to create XML object from string using xml-mapping in Ruby - ruby

I'm using xml-mapping in Ruby (on Sinatra) for some XML stuff. Generally I follow this tutorial: http://xml-mapping.rubyforge.org/. I can create objects and write them to XML strings using
login.save_to_xml.to_s
But when I try
login = Login.load_from_xml(xml_string)
I get the following error:
XML::MappingError - no value, and no default value: Attribute username not set (XXPathError: path not found: username):
Here is the XML string I receive:
<login><username>ali</username><password>baba</password></login>
This is what the class looks like:
class Login
include XML::Mapping
text_node :username, "username"
text_node :password, "password"
end
So the class name is the same, the nodes are named the same. I actually get the exact same string when I create an instance of my object and fill it with ali/baba:
test = Login.new
test.username = "ali"
test.password = "baba"
p test.save_to_xml.to_s
<login><username>ali</username><password>baba</password></login>
What am I missing?
Thanks,
MrB
EDIT:
When I do
test = login.save_to_xml
And then
login = Login.load_from_xml(test)
it works. So the problem seems to be that I'm passing a string, while the method is expecting.. well, something else. There is definitely a load_from_xml(string) method in the rubydocs, so not sure what to pass here. I guess I need some kind of reverse to_s?

It looks like you save_to_xml creates a REXML::Element. Since that works, you may want to try:
Login.load_from_xml(REXML::Document.new(xml_string).root)
See the section on "choice_node" for a more detailed example http://xml-mapping.rubyforge.org/

Related

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

Ruby String to access an object attribute

I have a text file (objects.txt) which contains Objects and its attributes.
The content of the file is something like:
Object.attribute = "data"
On a different file, I am Loading the objects.txt file and if I type:
puts object.attribute it prints out data
The issue comes when I am trying to access the object and/or the attribute with a string. What I am doing is:
var = "object" + "." + "access"
puts var
It prints out object.access and not the content of it "data".
I have already tried with instance_variable_get and it works, but I have to modify the object.txt and append an # at the beginning to make it an instance variable, but I cannot do this, because I am not the owner of the object.txt file.
As a workaround I can parse the object.txt file and get the data that I need but I don't want to do this, as I want take advantage of what is already there.
Any suggestions?
Yes, puts is correctly spitting out "object.access" because you are creating that string exactly.
In order to evaluate a string as if it were ruby code, you need to use eval()
eg:
var = "object" + "." + "access"
puts eval(var)
=> "data"
Be aware that doing this is quite dangerous if you are evaluating anything that potentially comes from another user.

Chef attributes "no implicit conversion of String into Integer"

I'm writing a chef recipe which simply creates a database config file, but I'm stumped simply access the attributes. I have a few PHP applications being deployed to each instance, and OpsWorks uses the same recipes for everyone, so I have a few different settings in the attributes file.
attributes/database-settings.rb
# API
default[:api][:path] = 'app/config/database.php';
default[:api][:host] = 'test';
default[:api][:database] = 'test';
default[:api][:username] = 'test';
default[:api][:password] = 'test';
recipes/database-settings.rb
Chef::Log.info("Database settings!");
node[:deploy].each do |application, deploy|
if node.has_key?(application)
Chef::Log.info("Application: #{application}");
path = node["api"]["path"]; # ERROR HAPPENING HERE
Chef::Log.info("Path: #{path}");
template path do
source "database.erb"
mode 0440
variables({
:host => node["api"]["host"],
:database => node["api"]["database"],
:username => node["api"]["username"],
:password => node["api"]["password"]
})
end
end
end
The error I'm getting is no implicit conversion of String into Integer. I've tried creating and accessing the settings in every way I can think of, such as...
node[:api][:path] # no implicit conversion of Symbol into Integer
node['api']['path'] # no implicit conversion of String into Integer
node[:api].path # undefined method `path' for #<Chef::Node::ImmutableArray:0x007fa4a71086e8>
node[application][:path] # no implicit conversion of Symbol into Integer
I'm sure there's something very obvious I'm doing wrong here, but I've tried everything I can think of an I just can't seem to find any way of getting this to work?! Ideally I'd like to use a variable where I can "api", but using an if/else wouldn't be too terrible for 3 apps...
That is a common error seen when you try to access an object thinking it is a hash, but is actually an array. In fact, from one of your errors, it can be read that node["api"] is a Chef::Node::ImmutableArray.
Ok so the problem wasn't really that I was accessing the config wrongly, it was that the different attribute files were all being merged into a single config and I didn't realise this.
I had these config files...
attributes/database_settings.rb
default[:api][:path] = 'app/config/database.php';
default[:api][:username] = 'example';
attributes/writable_directories.rb
default[:api] = ['public/uploads', 'storage/cache'];
When I tried to access default[:api][:path] I was actually accessing the array of directories when seemed to override the database settings attributes. Moving these into default[:directories][:api] and default[:database][:api][:path] etc fixed this.
Note that you will also get this error if you accidentally enter a space between "node" and the items indexing it:
node[:foo][:bar]
will work, while
node [:foo][:bar]
will throw this exception. It can be hard to spot.

How to check if a key exists in a Ruby hash?

I am using search of Net::LDAP, the returned entry is something like this.
#<Net::LDAP::Entry:0x7f47a6491c00
#myhash=
{:loginshell=>["/bin/bash"],
:cn=>["M... R..."],
:homedirectory=>["/mnt/home/m..."],
:uid=>["m..."],
:userpassword=>["{CRYPT}$1$3zR/C...$R1"],
...
}>
I tried to do the following, but failed.
(1)
e = entry.to_hash
e.has_key? "uid"
(2)
entry.has_key? "uid"
The first error says "to_hash" undefined, the second "has_key" undefined. Then I really don't know how to do it, basically I want to find if "uid" is present and if so get its correspondent value.
Thank you very much for the tip.
BTW, it only responds to "entry.uid", but if the search key is provided as a string, how to do that? for example,
def get_value(key)
if entry has key
return key's value
end
end
:uid is a Symbol. That's not a String.
try this:
e.has_key? :uid
The key "uid" doesn't exist. Try
e = Entry.new.myhash
e.has_key?(:uid)
That should return true. If that gives you an error, the problem might lie in your class. Make sure that myhash is defined in the initialize method, and that you use a getter method (or attr_reader) to be able to access the variable. You could use
attr_reader :myhash
right before the initialize method.

is there any plugin for complicated searching with mongoid (like meta_search for ActiveRecord)?

I tried meta_search, but after adding "include MetaSearch::Searches::ActiveRecord" into my model, it raised an error as "undefined method `joins_values'" when run "MyModel.search(params[:search])"
I think I dont need full text, so I think following gems are not suitable for my project now::
mongoid_fulltext
mongoid-sphinx
sunspot_mongoid
mongoid_search
I tried a old gem named scoped-search
I can make it work for example:
get :search do
#search = Notification.scoped_search(params[:search]
search_scope = #search.scoped
defaul_scope = current_user.notifications
result_scope = search_scope.merge defaul_scope
#notifications = result_scope
render 'notifications/search'
end
but it will be allow to call any scopes in my model.
Is there any "best practice" for doing this job ?
If you want limit the scope you want use on your scoped_search you can filter your params[:search] like :
def limit_scope_search
params[:search].select{|k,v| [:my_scope, :other_scope_authorized].include?(k) }
end

Resources