Why does ::TableView.model return nilClass when Nokogiri is used? - ruby

EDIT
The problem was with something else so the trouble wasn't really Qt, still I don't know why this happened.
The thing was that in the method display_filesize #yt.get_filesize(row_id, format) I used Nokogiri to parse the XML. I don't know if the XML was corrupted (it was loaded from quvi), but it was definitely the culprit. After switching to XMLSimple everything works fine.
The code I used:
def get_filesize(video_id, format)
video = #videos[video_id]
if video.formats[format].empty?
to_parse = `quvi --xml --format #{format} #{video.player_url}`
parsed = Nokogiri.parse(to_parse)
video.formats[format] = { :size => parsed.at('length_bytes').text,
:url => parsed.at('link').at('url').text }
end
video.formats[format][:size]
end
Now I use something like this:
def get_filesize(video_id, format)
video = #videos[video_id]
if video.formats[format].empty?
to_parse = `quvi --xml --format #{format} #{video.player_url}`
parsed = XmlSimple.xml_in(to_parse, {'KeyAttr' => 'name'})
video.formats[format] = { :size => parsed['link'][0]['length_bytes'][0],
:url => URI.decode(parsed['link'][0]['url'][0]) }
end
video.formats[format][:size]
end
It works beautifully. Still, I don't know why it crashed. This is the real question.
OLD QUESTION
I have a Qt::TableView that contains Qt::StandardItemModel. A row in the model consists of text, Qt::PushButton, checkbox and Qt::ComboBox. It works like this:
The user is presented with text values and can explore further if they want to.
The user clicks Qt::PushButton and the next cell is populated with a Qt::ComboBox containing other possible values to choose from.
If the user chooses an option from Qt::ComboBox, magic happens, objects get created, hashes populated and the cell on the right gets populated with appropriate text (through a Qt::StandardItem)
Then the checkbox can be checked.
After selecting the rows the user wants, a Qt::PushButton located outside of the Qt::TableView can be clicked. It then iterates through the model, tests if the checkbox is selected and should it be, tries to access the value in the appropriate ComboBox.
The problem is, when I insert code that tries to access the Qt::ComboBox, I can't insert the Qt::StandardItem, because I can't get the model, because Qt::TableView.model returns NilClass (at some point).
I don't know why and how this happens. It's a random thing, sometimes the value of Qt::ComboBox can be changed a couple times, sometimes the first try ends with an error.
Here is how I create the Qt::StandardItem:
def display_filesize
row_id = row_id_from_object_name(sender.objectName)
format = sender.currentText
filesize = #yt.get_filesize(row_id, format) # get the text
filesize_item = Qt::StandardItem.new("#{(filesize.to_i/1024/1024)} MB ")
# #tc simply stores the indexes of columns so I can access them easily
#ui.tableView.model.setItem(row_id, #tc[:filesize], filesize_item)
end
And here is how I try to access the Qt::ComboBox value:
model = #ui.tableView.model
checked = model.rowCount.times.map do |i|
if model.item(i, #tc[:check]).checkState == Qt::Checked
# if I remove the following two lines it works...
index = model.index(i, #tc[:formats])
format = #ui.tableView.indexWidget(index).currentText
#yt.videos[i][format]
end
end
And this is the error I am trying to get rid of:
searcher.rb:86:in `display_filesize': undefined method `index' for nil:NilClass (NoMethodError)
from /var/lib/gems/1.9.1/gems/qtbindings-4.8.3.0/lib/Qt/qtruby4.rb:469:in `qt_metacall'
from /var/lib/gems/1.9.1/gems/qtbindings-4.8.3.0/lib/Qt/qtruby4.rb:469:in `method_missing'
from /var/lib/gems/1.9.1/gems/qtbindings-4.8.3.0/lib/Qt/qtruby4.rb:469:in `exec'
from qutub-player.rb:17:in `<main>'

Related

Concept for recipe-based parsing of webpages needed

I'm working on a web-scraping solution that grabs totally different webpages and lets the user define rules/scripts in order to extract information from the page.
I started scraping from a single domain and build a parser based on Nokogiri.
Basically everything works fine.
I could now add a ruby class each time somebody wants to add a webpage with a different layout/style.
Instead I thought about using an approach where the user specifies elements where content is stored using xpath and storing this as a sort of recipe for this webpage.
Example: The user wants to scrape a table-structure extracting the rows using a hash (column-name => cell-content)
I was thinking about writing a ruby function for extraction of this generic table information once:
# extracts a table's rows as an array of hashes (column_name => cell content)
# html - the html-file as a string
# xpath_table - specifies the html table as xpath which hold the data to be extracted
def basic_table(html, xpath_table)
xpath_headers = "#{xpath_table}/thead/tr/th"
html_doc = Nokogiri::HTML(html)
html_doc = Nokogiri::HTML(html)
row_headers = html_doc.xpath(xpath_headers)
row_headers = row_headers.map do |column|
column.inner_text
end
row_contents = Array.new
table_rows = html_doc.xpath('#{xpath_table}/tbody/tr')
table_rows.each do |table_row|
cells = table_row.xpath('td')
cells = cells.map do |cell|
cell.inner_text
end
row_content_hash = Hash.new
cells.each_with_index do |cell_string, column_index|
row_content_hash[row_headers[column_index]] = cell_string
end
row_contents << [row_content_hash]
end
return row_contents
end
The user could now specify a website-recipe-file like this:
<basic_table xpath='//div[#id="grid"]/table[#id="displayGrid"]'
The function basic_table is referenced here, so that by parsing the website-recipe-file I would know that I can use the function basic_table to extract the content from the table referenced by the xPath.
This way the user can specify simple recipe-scripts and only has to dive into writing actual code if he needs a new way of extracting information.
The code would not change every time a new webpage needs to be parsed.
Whenever the structure of a webpage changes only the recipe-script would need to be changed.
I was thinking that someone might be able to tell me how he would approach this. Rules/rule engines pop into my mind, but I'm not sure if that really is the solution to my problem.
Somehow I have the feeling that I don't want to "invent" my own solution to handle this problem.
Does anybody have a suggestion?
J.

Tkinter Error when using Entry Delete Method

In my game, I have an __init__ function which creates a set of seven entry boxes, like so:
self.string1 = StringVal()
self.entry1 = Entry(frame, textvariable = self.string1).grid(row = 4, column = 1, sticky = W)
This is copied six more times. This works.
At the end of the game, though, I want to delete the Entry box's text, using this code I found several places online:
self.entry1.delete(0, END)
I also tried using something else I found:
if self.entry1.get():
self.entry1.delete(0, END)
These both say that self.entry1 is a NoneType object, and has no method .get() or .delete(). Just to try things out, I substituted self.entry1.get() and self.entry1.delete(0,END) with self.string1.get(), etc. I also tried substituting .delete(0, END) with .delete(0.0, END). Neither of these worked either. I do not understand what I am doing wrong.
Thanks for your help!
When you do something like this:
self.foo = Button(...).grid(...)
... Then what gets stored in self.foo is the result of the call to grid(). This will always be None. You need to separate your widget creation from the loyout in order to save a reference to the created widgets.

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.

How do I delete an embedded doc from Mongoid?

I'm having some issues deleting my document using Mongoid...
The code actually does delete the gallery, but I get a browser error which looks like:
Mongoid::Errors::DocumentNotFound at /admin/galleries/delete/4e897ce07df6d15a5e000001
The suspect code is below:
def self.removeGalleryFor(user_session_id, gallery_id)
person = Person.any_in(session_ids: [user_session_id])
return false if person.count != 1
return false if person[0].userContent.nil?
return false if person[0].userContent.galleries.empty?
gallery = person[0].userContent.galleries.find(gallery_id) #ERROR is on this line
gallery.delete if !gallery.nil?
end
My Person class embeds one userContent which embeds many galleries.
Strangely enough I've got a couple of tests around this which work fine...
I'm really not sure what's happening - my gallery seems to be found fine, and is even deleted from Mongo.
Any ideas?
find throws an error if it can't find a document with the given id. Instead of checking presence of given gallery and returning nil if it doesn't exist, you directly ask mongodb while querying to remove any such gallery.
def self.remove_gallery_for(user_session_id, gallery_id)
user_session_id = BSON::ObjectId.from_string(user_session_id) if user_session_id.is_a?(String)
gallery_id = BSON::ObjectId.from_string(gallery_id) if gallery_id.is_a?(String)
# dropping to mongo collection object wrapped by mongoid,
# as I don't know how to do it using mongoid's convenience methods
last_error = Person.collection.update(
# only remove gallery for user matching user_session_id
{"session_ids" => user_session_id},
# remove gallery if there exists any
{"$pull" => {:userContent.galleries => {:gallery_id => gallery_id}}},
# [optional] check if successfully removed the gallery
:safe => true
)
return last_error["err"].nil?
end
This way you do not load the Person, you don't even get the data from monogdb to application server. Just get the gallery removed if it exists.
But you should prefer #fl00r's answer if you need to fire callbacks and switch to destroy instead of delete
def self.removeGalleryFor(user_session_id, gallery_id)
# person = Person.where(session_ids: user_session_id).first
person = Person.any_in(session_ids: [user_session_id])
if person && person.userContent && person.userContent.galleries.any?
gallery = person.userContent.galleries.where(id: gallery_id).first
gallery.delete if gallery
end
end
ps:
In Ruby usually under_score naming rather then CamelCase is used
Kudos to Rubish for pointing me to a solution that at least passes my tests - for some reason fl00r's code didn't work - it looks like it should, but doesn't for some reason...
Person.collection.update(
{"session_ids" => user_session_id},
{"$pull" => {'userContent.galleries' => {:_id => gallery_id}}},
:safe => true
)
=> this code will pass my tests, but then once it's running in sinatra it doesn't work.... so frustrating!
have posted this code with tests on github https://github.com/LouisSayers/bugFixes/tree/master/mongoDelete

Single Ruby Value in One Line From a Collection

I have a collection of objects. There are 3 properties in each object
'id', 'name', 'is_primary'
The collection of objects will usually have anywhere from 1 to 5 objects.
What I want to do is check the collection to see if is_primary is true. If so output the name, or at least return it.
I want to do this in 1 line of code if possible. I am trying to slim up this one line for erb output in rails. Later in the page i'll output them all. I thought I had it, but if I return nil it adds extra space which shifts all the html oddly.
Thanks.
Hmm, this doesn't quite work if no element is_primary...I'm still thinking...
c.detect(&:is_primary).name
Ok, how about:
((a = c.detect(&:is_primary)) && a.name).to_s
As it happens, it is OK in an erb template for the <%= expression to return nil, that just results in an empty string, so for that case you can use:
(a = c.detect(&:is_primary)) && a.name
Update: Responding to the first comment, I do have a test case that I didn't post...
class A; attr_accessor :is_primary, :name, :id; end
t = [A.new, A.new, A.new, (a = A.new; a.name = 'xyz'; a.is_primary = true; a)]
puts (a = t.detect(&:is_primary)) && a.name
puts ((a = [].detect(&:is_primary)) && a.name).to_s
Complementing #DigitalRoss, you can also write:
collection.detect(&:is_primary).try(:name) || "default_if_no_element_or_name"
(well, to be honest I prefer Ick's maybe over Rails' try: c.detect(&:is_primary).maybe.name)
Side note: IMHO a flag that can only be active for a row it's not such a good idea. You may have inconsistent states with more than one being active and you'll have worry about it when updating (transactions, and so on). Try to store the PK reference somewhere else (a parent model? a state model?).
I want to do this in 1 line of code if possible. I am trying to slim up this one line for erb output in rails. Later in the page i'll output them all.
No need for one-liners (funny since I just wrote one): move the code to yous models or helpers as appropriate and keep your views pristine.

Resources