Rails - can I call a controller method from my ActionCable coffeescript? - ruby-on-rails

I'm building a quiz app that leads all participants synchroneously through multiple stages:
Phase 0: Quiz has not started
Phase 1: Current question is being displayed
Phase 3: Result for current question (correct answer vs given answer, statistics etc.) is being displayed
Phase 4: Result for entire quiz is being displayed
If a participant loses his connection and reconnects, I want them to be able to immediately pick up where they left off.
I always save the current phase to the db. So I'm thinking of calling a method show_current_phase(curent_user) in the connected method of my ActionCable channel. Since I feel like it would make sense to place this method in the corresponding controller for my custom QuizSession model, I'd like to know:
Is it possible to call a controller method in my coffeescript (frontend) and have it return an object?
Or how do I update the DOM tree of only the user who reconnected without broadcasting to all the other participants?

You can just turning your .coffee file into an .haml or .erb template.
.erb example with js
# myscript.js
var myPath = '/some_path';
Will become
# myscript.js.erb
var myPath = <%= some_controller_path %> ;
.haml example with coffee
In case you are using haml, the process is not really different.
# myscript.coffee
myPath = '/some_path'
Will become
# myscript.coffee.haml
myPath = '#{ some_controller_path }'
Obviously, you can create .js.haml and .coffee.erb files as well.
In your case, you could have something like this in your .coffee.haml:
myFunction = ->
res = $.ajax(
url: '#{method_controller_path}'
method: 'GET'
data:
variableParam: '#{#my_controller_var}'
anotherParam: 'anotherParam'
dataType: 'json'
success: (res) ->
res
)
doSomething res

Related

How can I use Cucumber to test drag and drop file uploads? [duplicate]

I have a web page that opens a div when you click a button. This div allows you to drag a file from your desktop onto its area; the file then gets uploaded to the server. I'm working with the Ruby implementation of Selenium.
By using the JavaScript debugger in Firefox, I can see that an event called "drop" is being passed to some JavaScript code "handleFileDrop(event)". I presume that if I were to create a mock event and fire it somehow that I could trigger this code.
If found an interesting article that seemed to point me in a promising direction, but I'm still short of figuring it all out. I am able to pass JavaScript to the page using Selenium's get_eval method. Calling methods using this.browserbot is getting me the elements I need.
So:
How do I build the file object that
needs to be part of the mock drop
event?
How do I fire the drop event
such that it gets picked up as if I
had dropped a file in the div?
I post an RSpec test that simulate files drag and drop using Selenium webdriver.
It use jQuery to make and trigger a fake 'drop' event.
This code simulate drag and drop of a single file. For sake of simplicity I've stripped code that allow multiple files dropping. Tell me if you need it.
describe "when user drop files", :js => true do
before do
page.execute_script("seleniumUpload = window.$('<input/>').attr({id: 'seleniumUpload', type:'file'}).appendTo('body');")
attach_file('seleniumUpload', Rails.root + 'spec/support/pdffile/pdfTest.pdf')
# Trigger the drop event
page.execute_script("e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : seleniumUpload.get(0).files } }; $('#fileDropArea').trigger(e);")
end
it "should ..." do
should have_content '...'
end
P.S.: remember to replace #fileDropArea with ID of your drop area.
P.P.S: don't use evaluate_script in place of execute_script, otherwise selenium get stuck evaluating complex jQuery objects!
UPDATE:
I've write a method you can reuse and do the stuff written above.
def drop_files files, drop_area_id
js_script = "fileList = Array();"
files.count.times do |i|
# Generate a fake input selector
page.execute_script("if ($('#seleniumUpload#{i}').length == 0) { seleniumUpload#{i} = window.$('<input/>').attr({id: 'seleniumUpload#{i}', type:'file'}).appendTo('body'); }")
# Attach file to the fake input selector through Capybara
attach_file("seleniumUpload#{i}", files[i])
# Build up the fake js event
js_script = "#{js_script} fileList.push(seleniumUpload#{i}.get(0).files[0]);"
end
# Trigger the fake drop event
page.execute_script("#{js_script} e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : fileList } }; $('##{drop_area_id}').trigger(e);")
end
Usage:
describe "when user drop files", :js => true do
before do
files = [ Rails.root + 'spec/support/pdffile/pdfTest1.pdf',
Rails.root + 'spec/support/pdffile/pdfTest2.pdf',
Rails.root + 'spec/support/pdffile/pdfTest3.pdf' ]
drop_files files, 'fileDropArea'
end
it "should ..." do
should have_content '...'
end
end
As #Shmoopy asked for it, here's a C# translation of the code provided by #micred
private void DropImage(string dropBoxId, string filePath)
{
var javascriptDriver = this.Driver as IJavaScriptExecutor;
var inputId = dropBoxId + "FileUpload";
// append input to HTML to add file path
javascriptDriver.ExecuteScript(inputId + " = window.$('<input id=\"" + inputId + "\"/>').attr({type:'file'}).appendTo('body');");
this.Driver.FindElement(By.Id(inputId)).SendKeys(filePath);
// fire mock event pointing to inserted file path
javascriptDriver.ExecuteScript("e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : " + inputId + ".get(0).files } }; $('#" + dropBoxId + "').trigger(e);");
}
You can use Blueduck Sda (http://sda.blueducktesting.com)
Is an OSS that has implemented ALL selenium functions (It works with selenium RC) but it allows you to automate Windows actions. So you can test web, and interact with the OS.
So you can make your test, and then, just tell the mouse to click on the element and drop it where you want!
Nice testing!
Note: you should also add
e.originalEvent.dataTransfer.types = [ 'Files' ];

Nokogiri Timeout::Error when scraping own site

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...

Using coffescript in rails views doesn't work properly

I'm trying to use coffescript as views in Rails 3.2.11
I have create.js.coffee with the following lines:
is_valid = <%=#model.valid?%>
if is_valid
res = confirm("Are you sure you want to continue?")
if(res)
<%=#model.activate%>
window.location.href = "/blabla/models"
else
return
else
$('.form .field_with_errors').removeClass('field_with_errors')
jw_funcs.respond_with_error(<%=#response_invalid%>)
The problem is that the line of code <%=#model.activate%>
is executed every time. I think it depends on the fact that the erb engine runs independently from the coffee engine; If so, how can I do this ?
You really weren't expecting this coffee code to call your model method from the client's browser, were you?
Wrap #model.activate into its own controller action, which will be called by clients if the confirmation is given. Something like this:
res = confirm("Are you sure you want to continue?")
if(res)
$.ajax('/models/1234/activate', ...)
else
return

Why will my google doubleclick code work when converted to coffeescript if I use the "window" object but not if I use "#" object

My understanding is that inside of a coffeescript function, "this" or "#" is equal to "window" (at least in the context of Rails). Why is it then that I can get this code to work:
window.googletag = window.googletag or {}
window.googletag.cmd = window.googletag.cmd or []
window.googletag.cmd.push ->
window.googletag.defineSlot('/1003175/ad-name-here', [336, 280], 'div-gpt-ad-1349373630997-0').addService(window.googletag.pubads())
window.googletag.pubads().enableSingleRequest()
window.googletag.enableServices()
but not this code
#googletag = #googletag or {}
#googletag.cmd = #googletag.cmd or []
#googletag.cmd.push ->
#googletag.defineSlot('/1003175/ad-name-here', [336, 280], 'div-gpt-ad-1349373630997-0').addService(#googletag.pubads())
#googletag.pubads().enableSingleRequest()
#googletag.enableServices()
When I place in my code alert(# == window) I get true.. if they are the same then why would one work but not the other? Is there not a more graceful way to write this code then appending window to every instance of the word googletag?
In coffeescript, the # is equivalent to this, but the value of this is dependent on your current scope within the code. In your example, alert(# == window) returns true because in that context this is the window. But when you use it in another context, for example inside a function definition (#googletag.cmd.push -> ...) then it will get the context of whatever scope that function is called from.
In the end this is not a coffeescript issue but a Javascript issue. I'd recommend reading up a bit more on this, it's a somewhat confusing concept at first.
Here's one article that helped me understand the concept better: http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/

Recursive method not returning to previous line position in caller

I have been trying to find a solution to this for some time. I have found questions and answers on recursion but nothing that seemed to fit this particular situation.
I have written a class which should go through the given folder and all subfolders and rename files and folders if a particular search pattern is found.
Everything works as expected the replaceAllInDir gets called, it replaces files and folders if needed. The next step then is to do the same for all subfolders within the given folder.
So a subfolder gets identified and replaceAllInDir gets called from within itself. Let's assum the particular subfolder called does not contain any subfolders. I would then expect that we return to the parent folder and continue looking for other subfolders. But instead control is not returned to the parent calling method and the program ends.
I am aware of other ways of solving the actual use case, but I cannot explain the behaviour of ruby.
class MultiFileAndFolderRename
attr_accessor :rootDir, :searchPattern, :replacePattern
def initialize(rootDir, searchPattern, replacePattern)
#rootDir = rootDir
#searchPattern = searchPattern
#replacePattern = replacePattern
end
def execute
replaceAllInDir(#rootDir)
end
def getValidDirEntries(dir)
dirList = Dir.entries(dir)
dirList.delete('.')
dirList.delete('..')
dirList
end
def replaceAllInDir(currentDir)
Dir.chdir(currentDir)
puts "Processing directory: " + Dir.pwd
dirList = getValidDirEntries(currentDir)
dirList.each { |dirEntry|
attemptRename(dirEntry)
}
dirList = getValidDirEntries(currentDir)
dirList.each { |dirEntry|
if File.directory?(dirEntry)
newDir = currentDir + '\\' + dirEntry
rntemp = MultiFileAndFolderRename.new(newDir, 'searchString', 'replaceString')
rntemp.replaceAllInDir(newDir)
end
}
end
def attemptRename(dirEntry)
if dirEntry.match(#searchPattern)
newname = dirEntry.to_s.sub(#searchPattern, #replacePattern)
FileUtils.mv(dirEntry.to_s, newname)
end
end
end
You have a bug. The first line of replaceAllInDir() is Dir.chdir(). chdir() changes the directory of the current process on a global scale. It's not call-stack dependent. So later when you move into a subdirectory and change into that, the change becomes permanent even if you return from the recursion.
You need to change back to the correct directory after any call to replaceAllInDir(). For example:
...
dirList.each { |dirEntry|
if File.directory?(dirEntry)
....
rntemp.replaceAllInDir(newDir)
Dir.chdir(currentDir) # <- Restore us back to the correct directory
end
}
I have tried your code, and I have found numerous errors in it. Perhaps if you fix them, your idea is working.
You should include in a library like that a part at the end that allows to call it from the shell: MultiFileAndFolderRename.new(ARGV[0], ARGV[1], ARGV[2]).execute if __FILE__ == $0 This ensures when you call the ruby code from the shell by ruby rename.rb test old new, your class will be instantiated, and the search and replace pattern will be set accordingly.
You shouldn't set the current directory, because that ensures that the line getValidDirEntries(currentDir) will not work. If you eg. call it for the directory test, and then change your current directory to test, inside the directory, getValidDirEntries('test') will not work like expected.
You should use only forward slashes instead of the double backward ones. So your code will work on Linux and Mac OS X as well.
When you instantiate the new instance of MultiFileAndFolderRename (which is not necessary), the arguments to the initializer are the wrong ones. Instead, you should use your current instance and just call self.replaceAllInDir(newDir) instead of rntemp = MultiFileAndFolderRename.new(newDir, 'searchString', 'replaceString');rntemp.replaceAllInDir(newDir).
I think the wrong instantiation is the major reason why it works not as expected, but the others should be fixed as well.

Resources