ProMotion lifecycle methods running out of order - rubymotion

I have been losing my mind over the last couple of weeks due to some very strange behaviour from ProMotion / RedPotion.
I have narrowed down the strange behaviour to a seemingly out of order execution of ProMotion's lifecycle methods and an API call.
I have a Screen that will get info from my API and display an image based on the image url returned from the API. I have oversimplified my project to a Screen, a Model and a Stylesheet as follows:
TestScreen:
class TestScreen < PM::Screen
title "Your title here"
stylesheet TestScreenStylesheet
def on_load
mp "ran on_load"
#face_image = append!(UIImageView, :face_image)
mp "getting data from API"
Face.get(1) do |response, face|
if response.success?
mp "face returned from API:"
mp face.inspect
#face = face
else
#face = [{attributes: {name: "No faces found"}}]
end
end
mp "should have printed data obtained from API"
end
def will_appear
mp "ran on_will_appear"
mp "face in will_appear:"
if #face
rmq(:face_image).attr(remote_image: #face.picture)
else
mp "#face is nil!!!"
end
end
end
Stylesheet:
class TestScreenStylesheet < ApplicationStylesheet
def setup
end
def root_view(st)
st.background_color = color.white
end
def face_image(st)
st.frame = {l: 30, t: 140, w: 250, h: 250}
st.placeholder_image = image.resource("placeholder_image.png")
end
end
Model:
class Face
attr_accessor :id, :name, :description, :local_description, :picture, :bio_picture, :star_ranking, :status, :facetype_id, :answers
def initialize(response)
#id = response[:data][0][:id]
#name = response[:data][0][:attributes][:name]
#description = response[:data][0][:attributes][:description]
#local_description = response[:data][0][:attributes][:local_description]
#picture = response[:data][0][:attributes][:picture]
#bio_picture = response[:data][0][:attributes][:bio_picture]
#star_ranking = response[:data][0][:attributes][:star_ranking]
#status = response[:data][0][:attributes][:status]
#facetype_id = response[:data][0][:attributes][:facetype_id]
#answers = response[:data][0][:attributes][:answers]
end
def self.get(category_id,&callback)
ApiClient.client.get "random_face?mycategory=#{category_id}" do |response|
model = nil
if response.success?
model = self.new(response.object)
end
callback.call(response, model)
end
end
end
I have placed printout commands (mp) so that I can figure out what is being executed when, and as you can see from the results below, everything is out of order:
"ran on_load"
"getting data from API"
"should have printed data obtained from API"
"ran on_will_appear"
"face in will_appear:"
"#face is nil!!!"
"face returned from API:"
"#<Face:0x113c37c90 #id=\"1\" #name=\"Leonel Messi\" #description=\"Leonel Messi es un jugador portugués de fútbol, 4 veces ganador del Balón de Oro\" #local_description=\"translation missing: en.Leonel Messi_description\" #picture=\"default_url\" #bio_picture=\"default_url\" #star_ranking=1 #status=\"active\" #facetype_id=nil #answers=\"[\\\"Kun Aguero\\\", \\\"Nicolas Shevchenko\\\", \\\"Leonel Messi\\\", \\\"Clarence Seedorf\\\"]\">"
The on_load method fires first, as expected, but the API call, RIGHT IN THE MIDDLE OF THE on_load method, fires last. Therefore the image attrib I am trying to set in the view_did_load method is nil and fails.
I come from a Rails background and I am new to RubyMotion / ProMotion / RedPotion so I may be going about this all wrong, but it certainly seems something is very wrong here.

As andrewhavens pointed out here:
https://github.com/infinitered/redpotion/issues/164
the reason this happens is that my API call is asynchronous, so it will not finish executing and set the instance variable in time for the will_appear method to use it (it will still be nil at the time will_appear runs).
In order to set the attributes properly, they must be set AFTER the API call completes, for example in the callback itself like this:
...
if response.success?
mp "face returned from API:"
mp face.inspect
#face = face
rmq(:face_image).attr(remote_image: #face.picture)
else
...
then it will work just fine.
Hope this saves someone hours of troubleshooting.

Related

How to reset a mock invocation counter using Scalatra, Specs2, Mockito

I never expected that I will need to ask a question on this site because everything is already answered normally but with Scalatra... I haven't find a lot of information so here it is:
I'm not experienced with all that so maybe I'm missing something but from what I understand, if I want to test the API that I develop on Scalatra, I need to start the server everytime I run the test suit, right ?
Second question, how can I reset the invocation counter on a method so I don't have to calculate how many times the method has been called since the beginning of the test suite ? Right now using this give me more than one because it counts the previous test.
there was one(*instance*).*method*(*parameter*)
I can still get around the problem by either counting or putting the test as first test for now but it's not a sustainable solution...
Other thing that I found:
Reset method on the mock... not found
http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html#17
Isolating the test in a class scope:
We need to add
val servlet = new Servlet(eventRepoMock)
addServlet(servlet, "/*")
and we can't repeat the addServlet at every initialization
https://etorreborre.github.io/specs2/guide/SPECS2-3.5/org.specs2.guide.Isolation.html
Last thing that I try is:
servlet.repo = mock[EventRepo]
but repo being a value, I can't change it like this.
Neither of these "solutions" feel very clean so I was wondering if someone had a genius idea that can solve that mess !?
Thank you in advance !
EDIT:
Thanks to Eric's comment the above question are solve(that was easy) but now I have problem because I'm testing the get/post which are asynchronous call so resetting the mock does not happen at the right time... Any suggestion ?
Here's a simplified version of the code:
class EventServiceSpec extends ScalatraSpec with Mockito with Before { def is = s2"""
Event Service
GET an existing event
must return status 200 $get_status200
must return the event with id = :id $get_rightEventElement
must call findById once on the event repository $get_findByIdOnRepo
"""
lazy val anEvent = Event(1, "Some Event"
lazy val eventsBaseUrl = "/events"
lazy val existingEventUrl = s"$eventsBaseUrl/${anEvent.id}"
lazy val eventRepoMock = mock[EventRepository]
lazy val servlet = new Servlet(eventRepoMock)
addServlet(servlet, "/*")
def before = {
eventRepoMock.findById(anEvent.id) returns Option(anEvent)
eventRepoMock.findById(unexistingId) returns None
eventRepoMock.save(anEvent) returns Option(anEvent)
}
def get_status200 = get(existingEventUrl){
status must_== 200
}
def get_findByIdOnRepo = get(existingEventUrl){
// TODO count considering previous test... need to find a cleaner way
there was three(eventRepoMock).findById(anEvent.id)
}
All org.mockito.Mockito functions can still be used in a specs2 specification and reset is one of them.
Now, since you are sharing the state of a mock object across several examples, you not only need to reset the mock state before each example but you also need to make your specification sequential:
class EventServiceSpec extends ScalatraSpec with Mockito
with BeforeAll with BeforeEach {
def is = sequential ^ s2"""
Event Service
GET an existing event
must return status 200 $get_status200
must return the event with id = :id $get_rightEventElement
must call findById once on the event repository $get_findByIdOnRepo
"""
lazy val anEvent = Event(1, "Some Event")
lazy val eventsBaseUrl = "/events"
lazy val existingEventUrl = s"$eventsBaseUrl/${anEvent.id}"
lazy val eventRepoMock = mock[EventRepository]
lazy val servlet = new Servlet(eventRepoMock)
def beforeAll = addServlet(servlet, "/*")
def before = {
reset(eventRepoMock)
eventRepoMock.findById(anEvent.id) returns Option(anEvent)
eventRepoMock.findById(unexistingId) returns None
eventRepoMock.save(anEvent) returns Option(anEvent)
}
def get_status200 = get(existingEventUrl){
status must_== 200
}
def get_findByIdOnRepo = get(existingEventUrl){
there was one(eventRepoMock).findById(anEvent.id)
}
}

Rails Ancestry summing all nodes

I am using the Ancestry gem. Each node has a numerical value associated with it (:value). Each time I CRUD a node, I want to recalculate the whole tree so that each node ends up with the sum of the values of its children. (Leaf nodes stay unchanged.)
My controller actions contain this code to trigger the recalculation:
def create
...
#tree = Tree.new(tree_params)
if #tree.save
Tree.roots.each do |r|
recalc_tree(r)
end
redirect_to trees_url, notice: "Node was successfully created. " + note.to_s
else
render :new, notice: "There was an error saving this node. Please try again."
end
and in the private section at the bottom of the controller I have this custom helper method:
def recalc_tree(r)
total = 0
if r.has_children?
r.children.each do |c|
total = total + recalc_tree(c)
end
r.value = total
else
r.value
end
end
I don't get any errors in the browser (the node saves correctly) but neither does this update any of the values of the ancestor nodes.
I tried running the helper in the console but got an "undefined method" error:
>> helper.recalc_tree(Tree.roots.first)
Tree Load (0.0ms) SELECT "trees".* FROM "trees" WHERE "trees"."ancestry" IS NULL ORDER BY "trees"."id" ASC LIMIT 1
NoMethodError: undefined method `recalc_tree' for #<ActionView::Base:0x5fc82c0>
What am I doing wrongly?
The more rails-style way to do this is to use ActiveRecord's callback (http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html). Also since when a node is updated, only the updated node's ancestors will need to be updated, so we can save some CPU cycles by updating only ancestors along the path to the root.
Example:
class Tree < ActiveRecord::Base
after_save :update_ancestors
...
...
private
def update_ancestors
self.ancestors.each do |ancestor|
ancestor.value = ancestor.descendants.pluck(:value).sum
ancestor.save
end
end
end
I did not actually ran this code on my machine, but this is pretty much the direction to this problem!

Call multiple webservices from play 2

I am a play2.0-Scala-beginner and have to call several Webservices to generate a HTML page.
After reading the The Play WS API page and a very interesting article from Sadek Drobi I am still unsure what's the best way to accomplish this.
The article shows some code snippets which I don't fully understand as a Play beginner.
Figure 2 on page 4:
val response: Either[Response,Response] =
WS.url("http://someservice.com/post/123/comments").focusOnOk
val responseOrUndesired: Either[Result,Response] = response.left.map {
case Status(4,0,4) => NotFound
case Status(4,0,3) => NotAuthorized
case _ => InternalServerError
}
val comments: Either[Result,List[Comment]] =
responseOrUndesired.right.map(r => r.json.as[List[Comment]])
// in the controller
comment.fold(identity, cs => Ok(html.showComments(cs)))
What does the last line with the fold do? Should comment be comments? Haven't I group the last statement in an Async block?
Figure 4 shows how to combine several IO calls with a single for-expression:
for {
profile <- profilePromise
events <- attachedEventsPromise
articles <- topArticlesPromise
} yield Json.obj(
"profile" -> profile,
"events" -> events,
"articles" -> articles )
}
// in the controller
def showInfo(...) = Action { rq =>
Async {
actorInfo(...).map(info => Ok(info))
}
}
How can I use this snippet? (I am a bit confused by the extra-} after the for-expression.)
Should I write something like this?
var actorInfo = for { // Model
profile <- profilePromise
events <- attachedEventsPromise
articles <- topArticlesPromise
} yield Json.obj(
"profile" -> profile,
"events" -> events,
"articles" -> articles )
def showInfo = Action { rq => // Controller
Async {
actorInfo.map(info => Ok(info))
}
}
What's the best way to combine the snippets from figure 2 and 4 (error handling + composition of IO non-blocking calls)? (f.ex. I want to produce a Error 404 status code if any of the called webservice produce an Error 404).
Maybe someone knows a complete example of calling webservices in the play framework (cannot find an example in the play Sample applications or anywhere else).
I have to say that the article is wrong in the example you show in Figure 2. The method focusOnOk does not exist in Play 2.0. I assume the author of the article used a pre-release version of Play 2 then.
Regarding comment, yes it should be comments. The fold in the statement is operating on an Either. It takes 2 functions as parameters. The first is a function to apply if it is a left value. The second is a function to apply if it is a right value. A more detailed explanation can be found here: http://daily-scala.blogspot.com/2009/11/either.html
So what the line does is. If I have a left value (which meant I got an undesired response), apply the built-in identity function which just gives you back the value. If it has a right value (which means I got an OK response), make a new result that shows the comments somehow.
Regarding Async, it's not actually asynchronous. focusOnOk is a blocking function (a remnant from the old Java days of Play 1.x). But remember, that's not valid Play 2 code.
As for Figure 4, the trailing } is actually because it's a partial alternative of what's in Figure 3. Instead of the numerous promise flatMaps. You can do a for comprehension instead. Also, I think it should be userInfo(...).map instead of actorInfo(...).map.
The Play documentation you linked to actually already shows you a full example.
def feedTitle(feedUrl: String) = Action {
Async {
WS.url(feedUrl).get().map { response =>
Ok("Feed title: " + (response.json \ "title").as[String])
}
}
}
will get whatever is at feedUrl, and you map it to do something with the response which has a status field you can check to see if it was a 404 or something else.
To that end, the Figure 3 and 4 of your linked article should give you a starting point. So you'd have something like,
def getInfo(...) : Promise[String] = {
val profilePromise = WS.url(...).get()
val attachedEventsPromise = WS.url(...).get()
val topArticlesPromise = WS.url(...).get()
for {
profile <- profilePromise
events <- attachedEventsPromise
articles <- topArticlesPromise
} yield {
// or return whatever you want
// remember to change String to something else in the return type
profile.name
}
}
def showInfo(...) = Action { rq =>
Async {
getInfo(...).map { info =>
// convert your info to a Result
Ok(info)
}
}
}

RubyMotion >> How to make a POST?

I've been checking out RestKit and the GET works
#client.get("/?amount=5", delegate:self)
Does anyone know how to make a POST and receive the result?
The docs mention that it looks something like this -
#client.post("/ask", params:#paramvar, delegate:self)
What do you encapsulate #paramvar? I have tried it as an array, hash and even nil - however, none of them have yielded any results.
Take a look at the bubble wrap library. It includes some really nice HTTP helpers.
http://bubblewrap.io/
Found an example in the RubyMotion_Cookbook.
https://github.com/IconoclastLabs/rubymotion_cookbook/tree/master/ch_8/06_sendinghttppost
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
#window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
#window.rootViewController = RootController.new
url_string = "http://rubymotion-cookbook.herokuapp.com/post"
post_body = "bodyParam1=BodyValue1&bodyParam2=BodyValue2"
url = NSURL.URLWithString(url_string)
request = NSMutableURLRequest.requestWithURL(url)
request.setTimeoutInterval(30)
request.setHTTPMethod("POST")
request.setHTTPBody(post_body.to_s.dataUsingEncoding(NSUTF8StringEncoding))
queue = NSOperationQueue.alloc.init
NSURLConnection.sendAsynchronousRequest(request,
queue: queue,
completionHandler: lambda do |response, data, error|
if(data.length > 0 && error.nil?)
html = NSString.alloc.initWithData(data, encoding: NSUTF8StringEncoding)
p "HTML = #{html}"
elsif( data.length == 0 && error.nil? )
p "Nothing was downloaded"
elsif(!error.nil?)
p "Error: #{error}"
end
end
)
#window.makeKeyAndVisible
true
end
end
Source:
https://github.com/rubymotion/BubbleWrap
Insallation:-
in console run 'gem install bubble-wrap' or mention 'gem bubble-wrap' in Gemfile
Line to be added in 'app_delegate.rb'file(by this Bubblewrap api is available through out app):-
require 'bubble-wrap/http'
Sample code for syntax:-
BW::HTTP.get("https://api.github.com/users/mattetti", {credentials: {username: 'matt', password: 'aimonetti'}}) do |response|
p response.body.to_str # prints the response's body
end

ABPersonViewController error with RubyMotion

I am having trouble using the ABPersonViewController with RubyMotion. The error I'm getting is
Objective-C stub for message setDisplayedPerson:' typev#:^v' not precompiled. Make sure you properly link with the framework or library that defines this message.
I suspect this is due to RubyMotion not casting to the type IOS expects. I think ABPersonCreate() is returning a CFType but the displayedPerson setter is expecting it to be cast as a ABRecordRef (that's just a guess from the error messages)
Here's the sample code to see the problem (based on Apple's QuickContacts sample):
#Rakefile
$:.unshift("/Library/RubyMotion/lib")
require 'motion/project'
Motion::Project::App.setup do |app|
# Use `rake config' to see complete project settings.
app.name = 'contacts'
app.frameworks += ['AddressBook', 'AddressBookUI']
end
and
# app/app_delegate.rb
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
window.rootViewController = UINavigationController.alloc.init
window.rootViewController.wantsFullScreenLayout = true
window.makeKeyAndVisible
true
# This works
add_person('Alex', 'Rothenberg')
# This fails (is it a type casting problem?)
show_person_view_controller('Rothenberg')
end
def show_person_view_controller(name)
anError = nil
address_book = ABAddressBookCreate();
people = ABAddressBookCopyPeopleWithName(address_book, name);
person = people.first
picker = ABPersonViewController.alloc.init.autorelease
picker.personViewDelegate = self
puts "Should this be an AddressBookRef? #{person.inspect}" # => #<__NSCFType:0x8c3bec0>
picker.displayedPerson = person
# The previous line fails
puts "We never reach this line!"
self.navigationController.pushViewController(picker, animated:true)
end
def add_person(first_name, last_name)
error = nil
contact = ABPersonCreate()
ABRecordSetValue( contact, KABPersonFirstNameProperty, first_name, error )
ABRecordSetValue( contact, KABPersonLastNameProperty, last_name, error )
address_book = ABAddressBookCreate()
ABAddressBookAddRecord( address_book, contact, error )
ABAddressBookSave( address_book, error )
end
end
When you run it we are able to add to the address book in the add_person method but it fails in show_person_view_controller on the line picker.displayedPerson = person
$ rake
Build ./build/iPhoneSimulator-5.1-Development
Simulate ./build/iPhoneSimulator-5.1-Development/contacts.app
Should this be an AddressBookRef? #<__NSCFType:0x8da2900>
Objective-C stub for message `setDisplayedPerson:' type `v#:^v' not precompiled. Make sure you properly link with the framework or library that defines this message.
Any suggestions would be appreciated
This will work properly as of RubyMotion 1.12 (run sudo motion update).
Fixed a bug where performing Objective-C methods that accept
CFType objects
would crash the program (ex. [ABPersonViewController setDisplayedPerson:]).