RestKit: "article" & "articles" keyPaths - do I need 2 different descriptors? - restkit

I can get single "article" or an array of "articles". Mapping is the same. But keyPath will be "article" or "articles". Do I need 2 different descriptors? Or can I combine it into one somehow? (as an example - Ruby on Rails can recognize single-multiple automatically).
Currently I'm doing it separately, Single:
RKResponseDescriptor *articleDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:articleMapping method:RKRequestMethodAny pathPattern:nil keyPath:#"article" statusCodes:statusCodes];
[_manager addResponseDescriptor:articleDescriptor];
Multiple:
RKResponseDescriptor *articlesArrayDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:articleMapping method:RKRequestMethodAny pathPattern:nil keyPath:#"articles" statusCodes:statusCodes];
[_manager addResponseDescriptor:articlesArrayDescriptor];

You need multiple descriptors. Usually you would want to set the pathPattern parameter to something specific on your response descriptors anyway to aid performance and prevent the creating of unwanted objects as always setting the pathPattern to nil will mean that RestKit can't filter out invalid descriptors for the received data and will need to process them. So, any overlap of key paths in your responses will start to generate (usually empty) object creations that you don't want.

Related

Cypress - counting number of elements in an array that contain a specific string

Attempting to confirm that of all the schema in the head of a page exactly 3 of them should have a specific string within them. These schemas have no tags or sub classes to differentiate themselves from each other, only the text within them. I can confirm that the text exists within any of the schema:
cy.get('head > script[type="application/ld+json"]').should('contain', '"#type":"Product"')
But what I need is to confirm that that string exists 3 times, something like this:
cy.get('head > script[type="application/ld+json"]').contains('"#type":"Product"').should('have.length', 3)
And I can't seem to find a way to get this to work since .filter, .find, .contains, etc don't filter down the way I need them to. Any suggestions? At this point it seems like I either need to import a custom library or get someone to add ids to these specific schema. Thanks!
The first thing to note is that .contains() always yields a single result, even when many element match.
It's not very explicit in the docs, but this is what it says
Yields
.contains() yields the new DOM element it found.
If you run
cy.get('head > script[type="application/ld+json"]')
.contains('"#type":"Product"')
.then(console.log) // logs an object with length: 1
and open up the object logged in devtools you'll see length: 1, but if you remove the .contains('"#type":"Product"') the log will show a higher length.
You can avoid this by using the jQuery :contains() selector
cy.get('script[type="application/ld+json"]:contains("#type\": \"Product")')
.then(console.log) // logs an object with length: 3
.should('have.length', 3);
Note the inner parts of the search string have escape chars (\) for quote marks that are part of the search string.
If you want to avoid escape chars, use a bit of javascript inside a .then() to filter
cy.get('script[type="application/ld+json"]')
.then($els => $els.filter((index, el) => el.innerText.includes('"#type": "Product"')) )
.then(console.log) // logs an object with length: 3
.should('have.length', 3);

Any ar js multimarkers learning tutorial?

I have been searching for ar.js multimarkers tutorial or anything that explains about it. But all I can find is 2 examples, but no tutorials or explanations.
So far, I understand that it requires to learn the pattern or order of the markers, then it stores it in localStorage. This data is used later to display the image.
What I don't understand, is how this "learner" is implemented. Also, the learning process is only used once by the "creator", right? The output file should be stored and then served later when needed, not created from scratch at each person's phone or computer.
Any help is appreciated.
Since the question is mostly about the learner page, I'll try to break it down as much as i can:
1) You need to have an array of {type, URL} objects.
A sample of creating the default array is shown below (source code):
var markersControlsParameters = [
{
type : 'pattern',
patternUrl : 'examples/marker-training/examples/pattern-files/pattern-hiro.patt',
},
{
type : 'pattern',
patternUrl : 'examples/marker-training/examples/pattern-files/pattern-kanji.patt',
}]
2) You need to feed this to the 'learner' object.
By default the above object is being encoded into the url (source) and then decoded by the learner site. What is important, happens on the site:
for each object in the array, an ArMarkerControls object is created and stored:
// array.forEach(function(markerParams){
var markerRoot = new THREE.Group()
scene.add(markerRoot)
// create markerControls for our markerRoot
var markerControls = new THREEx.ArMarkerControls(arToolkitContext, markerRoot, markerParams)
subMarkersControls.push(markerControls)
The subMarkersControls is used to create the object used to do the learning. At long last:
var multiMarkerLearning = new THREEx.ArMultiMakersLearning(arToolkitContext, subMarkersControls)
The example learner site has multiple utility functions, but as far as i know, the most important here are the ArMultiMakersLearning members which can be used in the following order (or any other):
// this method resets previously collected statistics
multiMarkerLearning.resetStats()
// this member flag enables data collection
multiMarkerLearning.enabled = true
// this member flag stops data collection
multiMarkerLearning.enabled = false
// To obtain the 'learned' data, simply call .toJSON()
var jsonString = multiMarkerLearning.toJSON()
Thats all. If you store the jsonString as
localStorage.setItem('ARjsMultiMarkerFile', jsonString);
then it will be used as the default multimarker file later on. If you want a custom name or more areas - then you'll have to modify the name in the source code.
3) 2.1.4 debugUI
It seems that the debug UI is broken - the UI buttons do exist but are nowhere to be seen. A hot fix would be using the 'markersAreaEnabled' span style for the div
containing the buttons (see this source bit).
It's all in this glitch, you can find it under the phrase 'CHANGES HERE' in the arjs code.

Using one variable for multiple items data in descriptive programming

I know that with Descriptive programming you can do something like this:
Browser("StackOverflow").Page("StackOverflow").Link("text:=Go To Next Page ", "html tag:=A").Click
But is it possible to create some kind of string so I can assign more than one data value and pass it as single variable? I've tried many combinations using escape characters and I always get error.
For example in the case above, let's say I have more properties in the Page object, so I'd normally have to do something like this:
Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")...etc...
But I'd like to pass "name:=StackOverflow", "html id:=PageID" as a single variable, so when writing many objects I'd only have to write:
Browser(BrowserString).Page(PageString).WebEdit("name:=asdfgh")
And the first part would remain static, so if the parents' data needs to be modified I'd only have to modify two variables and not all the objects created in all libraries.
Is it possible?
If I was not clear enough please let me know.
Thank you in advance!
I think what you're looking for is UFT's Description object
This allows you finer grained control on the description since in descriptive programming all values are regular expressions but with Description you can turn the regular expression functionality off for a specific property.
Set desc = Description.Create()
desc("html tag").Value = "A"
desc("innertext").Value = "More information..."
desc("innertext").RegularExpression = False
Browser("Example Domain").Navigate "www.example.com"
Browser("Example Domain").Page("Example Domain").WebElement(desc).Click
If you want to represent this with plain string then it's a bit more of a problem, you can write a helper function but I'm not sure I would recommend it.
Function Desc(descString)
Set ret = Description.Create()
values = Split(descString, "::")
For Each value In values
keyVal = Split(value, ":=")
ret(keyVal(0)).Value = keyVal(1)
Next
Set Desc = ret
End Function
' Usage
Browser("StackOverflow").Page("StackOverflow").WebElement(Desc("html tag:=H2::innertext:=some text")).Click
Further reading about descriptive programming.
As an alternative to Motti's excellent answer, you could also Set a variable to match your initial descriptive object and then extend it as required:
Set myPage = Browser("StackOverflow").Page("name:=StackOverflow", "html id:=PageID")
after which you can then use
myPage.WebEdit("name:=asdfgh")
throughout the rest of the code, so long as the myPage object stays in scope...

Cocoa Scripting: Use special string types like raw data

My app has some raw data content I want to be able to offer to AppleScript so that it can be at least looked at, if not even handled by saving it to a file or setting it to some other object that supports it.
Now, I don't understand which data type is used to accomplish that.
See this output from Script Editor, for instance:
tell application "Script Editor"
the clipboard as record
--> {Unicode text:"text",
«class BBLM»:«data BBLM6C6C756E»,
string:"text"}
end tell
How do I return these «data ...», which are apparently a combination of a 4-char-code and hex-string-encoded bytes of the actual data.
I've tried returning an NSData object containing the raw bytes data from my scriptable property, but that doesn't work.
Update
It appears it has to do with implementing scripting<type>Descriptor and scripting<type>WithDescriptor. I cannot find any documentation on this other than it being used in the Sketch sample code. I assume these will be invoked for the type if I happen to define such a custom type in my Sdef.
However: I will not know the types I want to send in advance, so I cannot pre-define them in the Sdef. I'm more in the situation similar to the clipboard: I have clipboard-like data I want to return, so I only know their 4-char-types at runtime. Which means I won't be asked through these handlers. There must be some other way to generically create and receive these types, the same way the clipboard implementation does it.
RE: "...implementing scripting<Key>Descriptor and scripting<Key>WithDescriptor. I cannot find any documentation on this..."
The first place to start is "Key-Value Coding and Cocoa Scripting" section in the Cocoa Scripting Guide (2008). There are a whole slew of these methods that embed the type in the method name. Many are also documented in the Foundation's NSScriptKeyValueCoding Protocol Reference page, but you have to read the "Discussion" section to find them. For instance, in:
- (id)valueWithUniqueID:(id)uniqueID inPropertyWithKey:(NSString *)key
the discussion says: "The method valueIn<Key>WithUniqueID: is invoked if it exists."
So in a Widgets class, you would implement valueInWidgetsWithUniqueID:
scripting<Key>Descriptor and scripting<Key>WithDescriptor are special conversion handlers that are used when you use a element in your application's .sdef, which is why they show up in Sketch to handle the typeRGBColor data type, a list of 3 integers. I can't find these documented outside of the Sketch code either, but I can confirm that
scriptingRGBColorDescriptor
is called by methods in:
NSObject(NSScriptAppleEventConversion)
NSAppleEventDescriptor(NSScriptConversion)
RE: "However: I will not know the types I want to send in advance, so I cannot pre-define them in the Sdef."
There is a way to solve that problem: you can return a special list structure known as a User-Field Record (typeUserField). This record includes alternating Key and Value descriptors, and does not require anything to be defined in the SDEF.
Here's an item I posted on the ASOC mailing list last year:
http://lists.apple.com/archives/applescriptobjc-dev/2015/Jan/msg00036.html
And here's the code (using AppleScript-ObjectiveC code) to build the typeUserField record from an NSDictionary.
# ASOC implementation of - (NSAppleEventDescriptor *)scriptingRecordDescriptor for an NSDictionary
# Creates an empty record descriptor and an empty list descriptor, then
# Iterates over the dictionary and inserts descriptors for each key and each value into the list descriptor
# Finally, populates the record descriptor with the type 'usrf' and the list descriptor
on makeUserRecordDescriptor(aDict)
log aDict
set recordDescriptor to aedClass's recordDescriptor()
set listDescriptor to aedClass's listDescriptor()
set typeUserField to 1970500198 -- 'usrf'
set itemIndex to 1 -- AS records are 1-based
repeat with aKey in aDict's allKeys()
set aVal to aDict's valueForKey_(aKey)
-- The values can be several different types. This code DOES NOT handle them all.
set isStringValue to aVal's isKindOfClass_(nssClass's |class|) = 1
set isNumericValue to aVal's isKindOfClass_(nsnClass's |class|) = 1
set isBooleanValue to aVal's className()'s containsString_("Boolean") = 1
-- Insert a descriptor for the key into the list descriptor
set anItem to aedClass's descriptorWithString_(aKey)
listDescriptor's insertDescriptor_atIndex_(anItem, itemIndex)
set itemIndex to itemIndex + 1
-- Insert a descriptor (of the correct type for the value) into the list descriptor
if isStringValue
set anItem to aedClass's descriptorWithString_(aVal)
else if isBooleanValue
set anItem to aedClass's descriptorWithBoolean_(aVal's boolValue())
else if isNumericValue
set intValue to aVal's intValue()
set fpValue to aVal's doubleValue()
if intValue = fpValue
set anItem to aedClass's descriptorWithInt32_(aVal's intValue())
else
set anItem to aedClass's descriptorWithString_(aVal's stringValue) # TODO: 'doub'
end
else
set anItem to aedClass's descriptorWithString_("Unhandled Data Type")
end
listDescriptor's insertDescriptor_atIndex_(anItem, itemIndex)
set itemIndex to itemIndex + 1
end
recordDescriptor's setDescriptor_forKeyword_(listDescriptor, typeUserField)
return recordDescriptor
end
The magic lies in using NSAppleEventDescriptor. It offers a lot of initializers. It's the one that eventually holds any value that gets passed back to the calling AppleScript (or JXA or whatever uses the Scripting engine).
Apparently, any value returned to the Cocoa Scripting layer, such as strings as NSString and numeric values as NSNumber, end up being assign to a NSAppleEventDescriptor object, and converted by that step to the AppleEvent-internal format.
So, if I want to return a string of bytes, e.g. stored in an NSData object, all I have to do is this from my property method:
-(id)returnSomeBytes {
return [NSAppleEventDescriptor descriptorWithDescriptorType:'Raw ', myNSDataObject];
}
This will end up in AppleScript as «data Raw ...».
I now also understand why the scripting engine won't automatically convert NSData for me: It needs a type code, which NSData doesn't inherit.
The reverse works just as well - any such raw data gets passed to my code as a NSAppleEventDescriptor, which I can then decode accordingly.

How do I prevent multiple RKResponseDescriptors from matching?

I am using RestKit 0.20.0rc1 in an app that uses 2 entities:
A "note" (NoteClass).
A "set" (SetClass), which contains a collection of notes.
I have the following 2 response descriptors (among others):
// GET /sets/:setID/notes
// Get a set's notes. Response looks like this:
// {"notes": [ (array of NoteClass dictionaries) ],
// ...more stuff...
// }
RKResponseDescriptor *noteResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[NoteClass rkEntityMapping]
pathPattern:#"/sets/:setID/notes"
keyPath:#"notes"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:noteResponseDescriptor];
// GET /sets/:setID
// Get information about a set. Response looks like this:
// {"name": "My Set",
// "numNotes": 3,
// ...more stuff...
// }
RKResponseDescriptor *setResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[SetClass rkEntityMapping]
pathPattern:#"/sets/:setID"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:setResponseDescriptor];
When I request "/sets/:setID/notes", the noteResponseDescriptor matches (expected). However, the setResponseDescriptor also matches (unexpected). I believe this is because the response descriptor's path pattern matches the substring "/sets/:setID", and because key path is nil. As a result, when I make the request, the RKMappingResult I get back contains an array of NoteClass objects (expected) and a single empty SetClass object (unexpected).
How do I prevent the setResponseDescriptor from matching this notes endpoint? I can't add a key path to the setResponseDescriptor (yet), so my preference is for a solution that allows me to say something like "match /sets/:setID$", where "$" designates the end of the URL.
Turns out that there is now way to prevent multiple matches for your example (see the discussion here).
While a solution is on the way you have a few options for fixing this:
Change your API urls so it doesn't lead to ambiguities
Check the type of the mapped result objects and discard any unexpected objects
Modify [RKResponseDescriptor matchesPath:] as suggested in the discussion thread.
Each solution is flawed in it's own way though.

Resources