Nokogiri works fine for me in the console, but if I put it anywhere... Model, View, or Controller, it times out.
I'd like to use it 1 of 2 ways...
Controller
def show
#design = Design.find(params[:id])
doc = Nokogiri::HTML(open(design_url(#design)))
images = doc.css('.well img') ? doc.css('.well img').map{ |i| i['src'] } : []
end
or...
Model
def first_image
doc = Nokogiri::HTML(open("http://localhost:3000/blog/#{self.id}"))
image = doc.css('.well img')[0] ? doc.css('.well img')[0]['src'] : nil
self.update_attribute(:photo_url, image)
end
Both result in a timeout, though they work perfectly in the console.
When you run your Nokogiri code from the console, you're referencing your development server at localhost:3000. Thus, there are two instances running: one making the call (your console) and one answering the call (your server)
When you run it from within your app, you are referencing the app itself, which is causing an infinite loop since there is no available resource to respond to your call (that resource is the one making the call!). So you would need to be running multiple instances with something like Unicorn (or simply another localhost instance at a different port), and you would need at least one of those instances to be free to answer the Nokogiri request.
If you plan to run this in production, just know that this setup will require an available resource to answer the Nokogiri request, so you're essentially tying up 2 instances with each call. So if you have 4 instances and all 4 happen to make the call at the same time, your whole application is screwed. You'll probably experience pretty severe degradation with only 1 or 2 calls at a time as well...
Im not sure what default value of timeout.
But you can specify some timeout value like below.
require 'net/http'
http = Net::HTTP.new('localhost')
http.open_timeout = 100
http.read_timeout = 100
Nokogiri.parse(http.get("/blog/#{self.id}").body)
Finally you can find what is the problem as you can control timeout value.
So, with tyler's advice I dug into what I was doing a bit more. Because of the disconnect that ckeditor has with the images, due to carrierwave and S3, I can't get any info direct from the uploader (at least it seems that way to me).
Instead, I'm sticking with nokogiri, and it's working wonderfully. I realized what I was actually doing with the open() command, and it was completely unnecessary. Nokogiri parses HTML. I can give it HTML in for form of #design.content! Duh, on my part.
So, this is how I'm scraping my own site, to get the images associated with a blog entry:
designs_controller.rb
def create
params[:design][:photo_url] = Nokogiri::HTML(params[:design][:content]).css('img').map{ |i| i['src']}[0]
#design = Design.new(params[:design])
if #design.save
flash[:success] = "Design created"
redirect_to designs_url
else
render 'designs/new'
end
end
def show
#design = Design.find(params[:id])
#categories = #design.categories
#tags = #categories.map {|c| c.name}
#related = Design.joins(:categories).where('categories.name' => #tags).reject {|d| d.id == #design.id}.uniq
set_meta_tags og: {
title: #design.name,
type: 'article',
url: design_url(#design),
image: Nokogiri::HTML(#design.content).css('img').map{ |i| i['src']},
article: {
published_time: #design.published_at.to_datetime,
modified_time: #design.updated_at.to_datetime,
author: 'Alphabetic Design',
section: 'Designs',
tag: #tags
}
}
end
The Update action has the same code for Nokogiri as the Create action.
Seems kind of obvious now that I'm looking at it, lol. I dwelled on this for longer than I'd like to admit...
Related
I get a warning when running reek on a Rails project:
[36]:ArborReloaded::UserStoryService#destroy_stories has approx 8 statements (TooManyStatements)
Here's the method:
def destroy_stories(project_id, user_stories)
errors = []
#project = Project.find(project_id)
user_stories.each do |current_user_story_id|
unless #project.user_stories.find(current_user_story_id).destroy
errors.push("Error destroying user_story: #{current_user_story_id}")
end
end
if errors.compact.length == 0
#common_response.success = true
else
#common_response.success = false
#common_response.errors = errors
end
#common_response
end
How can this method be minimized?
First, I find that class and method size are useful for finding code that might need refactoring, but sometimes you really do need a long class or method. And there is always a way to make your code shorter to get around such limits, but that might make it less readable. So I disable that type of inspection when using static analysis tools.
Also, it's unclear to me why you'd expect to have an error when deleting a story, or who benefits from an error message that just includes the ID and nothing about what error occurred.
That said, I'd write that method like this, to reduce the explicit local state and to better separate concerns:
def destroy_stories(project_id, story_ids)
project = Project.find(project_id) # I don't see a need for an instance variable
errors = story_ids.
select { |story_id| !project.user_stories.find(story_id).destroy }.
map { |story_id| "Error destroying user_story: #{story_id}" }
respond errors
end
# Lots of services probably need to do this, so it can go in a superclass.
# Even better, move it to #common_response's class.
def respond(errors)
# It would be best to move this behavior to #common_response.
#common_response.success = errors.any?
# Hopefully this works even when errors == []. If not, fix your framework.
#common_response.errors = errors
#common_response
end
You can see how taking some care in your framework can save a lot of noise in your components.
My website allows users to add text to an image, and displays the result to them via an Ajax.request. Sometimes it works great, but other times the image is incomplete and javascript records the error "Image corrupt or truncated."
How can I make sure the file is completely written before sending the Ajax response back to the browser?
VIEW
new Ajax.Request('<%= url_for(:action => "update_image", :id => #user_image.id) %>?greeting=' + encodeURIComponent(elmgreeting.value), { onSuccess: document.getElementById("card_image").src=card_filename });
MODEL
def create_card
img = Magick::Image.read(self.input_image).first
# ... add the greeting to the image
img.write(self.card_filename)
self.card_width = img.columns
self.card_height = img.rows
self.card_size = img.filesize
end
CONTROLLER
def update_image
#user_image = UserImage.find(params[:id])
#user_image.greeting = params[:greeting]
#user_image.create_card
#user_image.save
render :layout => false
end
[I've also noticed the img.filesize value assigned to the card_size is often inaccurate, leading me to think it's being obtained too early, as well.)
I've tried everything I could find on stackoverflow and elsewhere, but just can't figure it out. Any help greatly appreciated.
FOUND MY PROBLEM: It was the Ajax request. It now works nicely:
new Ajax.Request('<%= url_for(:action => "update_image", :id => #user_image.id) %>?greeting=' + encodeURIComponent(elmgreeting.value), { onComplete:function(request){document.getElementById("card_image").src=card_filename}});
afaik the things you write should be already synchronous, there is probably something else that breaks things. what did your rails server log said? maybe there is an error happens and throws some errors and then your front-side tries to use it as an image source
I'm having a difficult time understanding the Rails API. I am trying to figure out a way to understand what I can call from certain points inside Rails, such as when I'm in a controller, so I wrote something to tell me all the methods that are available sorted by what Module/Class they fall under:
last_sig = ""
self.methods.each do |method|
#i_am = self.method(method).owner
#puts i_am.class
#places.push(self.method(method).owner)
m = self.method(method)
sig = "#{m.owner.class}: #{m.owner}"
if sig != last_sig
last_sig = sig
puts sig
end
puts " #{method}"
end
As an example, I find out (just using this as an easy example) that I can use the render() method and it is located at ActionController::Instrumentation, so then I look at the render() function there and it says:
render(*args)
# File actionpack/lib/action_controller/metal/instrumentation.rb, line 38
def render(*args)
render_output = nil
self.view_runtime = cleanup_view_runtime do
Benchmark.ms { render_output = super }
end
render_output
end
That is all is says, I don't understand how from this I could understand how it works, then I do some more searching and by "luck" I discover that it is documented in ActionView, and I wonder how I was able to know this? Anyway, any tips on how to read the API would be appreciated- It seems like many of the things in the API are not documented for a User, and I don't know if they are for the User or for the developers of Rails- I'm used to using a documentation like jQuery which seems much easier to Discover functionality by using-
I have this action in my controller:
def ad
#koder = #side.reklamers.pluck(:id) - [session[:log]]
#reklame = Reklamer.find(#koder.sample)
session[:log] = #reklame.id
render :text => "<span class='bannerid' data-id='#{#reklame.id}'></span><p style='margin-bottom: 7px;margin-top: 7px;font-size: 9px;text-align: center !important;'>Ad</p>#{#reklame.kode}"
end
It renders an HTML ad. This code is really fast. The problem is when I do try to example count view the response becomes VERY slow. 3s. Compared to 200ms!
#koder = #side.reklamers.pluck(:id) - [session[:log]]
#reklame = Reklamer.find(#koder.sample)
session[:log] = #reklame.id
#reklame.views += 1
#reklame.save
render :text => "<span class='bannerid' data-id='#{#reklame.id}'></span><p style='margin-bottom: 7px;margin-top: 7px;font-size: 9px;text-align: center !important;'>Ad</p>#{#reklame.kode}"
I have tried to add a resque background job with the same result..
What should I do?
Since views is really a counter maybe you should take a look at:
http://apidock.com/rails/v3.2.13/ActiveRecord/CounterCache/increment_counter
In your case it should look like this
Reklamer.increment_counter(:views, #reklame.id)
It should update only views column, don't run validations, callbacks etc. which will make it faster. But on the other hand 3s on simple save indicates that there is something else going wrong also.
First, try:
#reklame.update_attribute :views, #reklame.views + 1
See if that helps. If so, then you have some costly data validations going on that are slowing you down, but at least you know the source of the problem.
If that doesn't work, try wrapping that statement in
Thread.new { //code }
If it's still slow doing the save, then it's probably how your database is set up or something along those lines.
I am currently using MongoDB for tracking of various things in a Rails 2 app. I am using the following code to see if MongoDB is up and running and, depending upon the status, displaying a link or an "Offline" message.
This is only for admins, so it's not mission-critical, as the app will continue to run without MongoDB, but I do want to keep disabling the link in the menu when it's not running. However, I don't like the overhead of the below code (doesn't take long to run, but hope that there is a cleaner, faster way):
def verify_mongodb_status
begin
track = Track.first
#mongodb_running = true
rescue
#mongodb_running = false
logger.debug("***MongoDB not running.***")
notify_admin_about_errors("***MongoDB is not running***)
end
end
EDIT: I forgot to mention that I'm already doing a before_filter for this; the method sits in application_controller.rb.
I decided to go with action_caching as there doesn't seem to be a great way to do this. The result was quite a large speed increase from ~120ms to ~16-25ms:
def verify_mongodb_status
begin
track = Track.first
#mongodb_running = true
rescue => e
#mongodb_running = false
logger.debug("***MONGODB OFFLINE***: #{e}")
notify_admin_about_errors("MongoDB", "MongoDB error:\n#{e}", nil)
expire_action :action => :verify_mongodb_status
return
end
end
I'm adding logic now to keep from getting bombarded by emails when MongoDB goes offline (1 is enough).