I have a implemented the post and user model where post model belongs to user model. I defined the ability model for authorisation so that only user who created the post can able to delete or update the post. i have the post controller like this:
def edit
#post = #topic.posts.find(params[:id])
authorize! :update, #post
end
Ability model:
class Ability
include CanCan::Ability
def initialize(user)
can :update, Post do |p|
p.user == user
end
can :destroy, Post do |p|
p.user == user
end
can :destroy, Comment do |c|
c.user == user
end
can :create, Post
can :create, Comment
end
end
What will be the rspec for the above model?
Error:
expected #<User id: nil, email: "", created_at: nil, updated_at: nil> to respond to `able_to?`
0) User Check for Abilities What User can do should be able to :create and #<Post id: nil, title: nil, body: nil, created_at: nil, updated_at: nil, topic_id: nil, image_file_name: nil, image_content_type: nil, image_file_size: nil, image_updated_at: nil, user_id: nil>
Failure/Error: it { should be_able_to(:create, Post.new) }
expected #<User id: nil, email: "", created_at: nil, updated_at: nil> to respond to `able_to?`
# ./spec/models/ability_spec.rb:8:in `block (4 levels) in <top (required)>'
expected #<User id: nil, email: "", created_at: nil, updated_at: nil> to respond to `able_to?`
0) User Check for Abilities What User can do should be able to :update and #<Post id: nil, title: nil, body: nil, created_at: nil, updated_at: nil, topic_id: nil, image_file_name: nil, image_content_type: nil, image_file_size: nil, image_updated_at: nil, user_id: nil>
Failure/Error: it { should be_able_to(:update, Post.new) }
expected #<User id: nil, email: "", created_at: nil, updated_at: nil> to respond to `able_to?`
# ./spec/models/ability_spec.rb:9:in `block (4 levels) in <top (required)>'
Based on the limited information you have provided, I'm going to share a sample spec which tests abilities.
describe "User" do
describe "Check for Abilities" do
let(:user) { FactoryGirl.create(:user) }
describe "What User can do" do
it { should be_able_to(:create, Post.new) }
it { should be_able_to(:update, Post.new) }
end
end
end
What I have provided at the top was a Sample, using which you have to build upon it. My updated answer
require "cancan/matchers"
describe "User" do
describe "abilities" do
user = User.create!
ability = Ability.new(user)
expect(ability).to be_able_to(:create, Post.new)
expect(ability).to_not be_able_to(:destroy, Post.new)
end
end
Let's start by refactoring your ability file a little bit. Is always a good idea to use a hash of conditions, rather than blocks. Therefore your ability file should look like:
class Ability
include CanCan::Ability
def initialize(user)
can [:update, :destroy], Post, user_id: user.id
can :destroy, Comment, user_id: user.id
can :create, Post
can :create, Comment
end
end
this is a shorter version of the one you have, and it does more.
Here is an example on how I would write the test, based on the cancancan documentation:
require 'cancan/matchers'
RSpec.describe Ability do
subject(:ability) { described_class.new(user) }
context 'when there is no user' do
let(:user){ nil }
it { is_expected.to_not be_able_to(:create, Post)
....
end
context 'when the user is present' do
let(:user){ User.new }
it { is_expected.to be_able_to(:create, Post) }
...
end
end
end
I let the rest of the tests to you.
Related
Summary
I'm developing an application where users can reserve and cancel reservations for classes. In a ReservationButtonView I two buttons that add and remove a user to a workout class respectively. Currently the button I show is based off whether the user's Firebase Auth uid is listed in a Firestore document.
I was having issues when rapidly tapping on the reservation button. Specifically, the reservationCnt would become inaccurate by showing more or less than the actual users reserved for a class.
The only way I have found to resolve this is be using a Firestore transaction that checks to see if a user is in a workout class already. If they are, addReservation() now does nothing. If they aren't, removeReservation() would also do nothing.
At first I thought I could just disable the button and via the logic still in place the code below (.disabled()), but that alone didn't work as I ran into the above described race conditions. What I found out is that arrayUnion and arrayRemove still succeed even when the object I'm looking to add is there and not there respectively. Meaning it is possible for my transaction to not remove a reservedUser that isn't there and also decrease the reservationCnt which can leave me with say no reserved users and a reservationCnt of -1
The Ask
Is there a better way to handle this reservation process? Can I accomplish this without a transaction for at least the removal of users in some way. Ideally, I'd like to have a spinner replace the button as I add or remove a user's reservation to indicate to the user that the app is processing the request. Perhaps I need two variables to manage the disabled() state instead of one?
MVVM Code Snippets
NOTE: I pulled out some button styling to make the code a bit less verbose
ReservationButtonView
struct ReservationButtonView: View {
var workoutClass: WorkoutClass
#ObservedObject var viewModel: WorkoutClassViewModel
#EnvironmentObject var authViewModel: AuthViewModel
var body: some View {
if checkIsReserved(uid: authViewModel.user?.uid ?? "", reservedUsers: workoutClass.reservedUsers ?? []) {
Button(action: {
viewModel.isDisabled = true
viewModel.removeReservation(
documentId: workoutClass.id!,
reservedUserDetails: ["uid": authViewModel.user?.uid as Any, "photoURL": authViewModel.user?.photoURL?.absoluteString ?? "" as Any, "displayName": authViewModel.user?.displayName ?? "Bruin Fitness Member" as Any],
uid: authViewModel.user?.uid ?? "")
}){
Label(
title: { Text("Cancel Reservation")
.font(.title) },
icon: { Image(systemName: "person.badge.minus")
.font(.title) }
)
}.disabled(viewModel.isDisabled)
} else{
Button(action: {
viewModel.isDisabled = true
viewModel.addReservation(
documentId: workoutClass.id!,
reservedUserDetails: ["uid": authViewModel.user?.uid as Any, "photoURL": authViewModel.user?.photoURL?.absoluteString ?? "" as Any, "displayName": authViewModel.user?.displayName ?? "Bruin Fitness Member" as Any],
uid: authViewModel.user?.uid ?? "")
}){
Label(
title: { Text("Reserve")
.font(.title) },
icon: { Image(systemName: "person.badge.plus")
.font(.title) }
)
}
.disabled(viewModel.isDisabled)
}
}
}
func checkIsReserved(uid: String, reservedUsers: [reservedUser]) -> Bool {
return reservedUsers.contains { $0.uid == uid }
}
WorkoutClassModel
struct reservedUser: Codable, Identifiable {
var id: String = UUID().uuidString
var uid: String
var photoURL: URL?
var displayName: String?
enum CodingKeys: String, CodingKey {
case uid
case photoURL
case displayName
}
}
struct WorkoutClass: Codable,Identifiable {
#DocumentID var id: String?
var reservationCnt: Int
var time: String
var workoutType: String
var reservedUsers: [reservedUser]?
enum CodingKeys: String, CodingKey {
case id
case reservationCnt
case time
case workoutType
case reservedUsers
}
}
WorkoutClassViewModel
class WorkoutClassViewModel: ObservableObject {
#Published var isDisabled = false
private var db = Firestore.firestore()
func addReservation(documentId: String, reservedUserDetails: [String: Any], uid: String){
let incrementValue: Int64 = 1
let increment = FieldValue.increment(incrementValue)
let addUser = FieldValue.arrayUnion([reservedUserDetails])
let classReference = db.document("schedules/Redwood City/dates/\(self.stateDate.dbDateFormat)/classes/\(documentId)")
db.runTransaction { transaction, errorPointer in
let classDocument: DocumentSnapshot
do {
print("Getting classDocument for docId: \(documentId) in addReservedUser()")
try classDocument = transaction.getDocument(classReference)
} catch let fetchError as NSError {
errorPointer?.pointee = fetchError
return nil
}
guard let workoutClass = try? classDocument.data(as: WorkoutClass.self) else {
let error = NSError(
domain: "AppErrorDomain",
code: -3,
userInfo: [
NSLocalizedDescriptionKey: "Unable to retrieve workoutClass from snapshot \(classDocument)"
]
)
errorPointer?.pointee = error
return nil
}
let isReserved = self.checkIsReserved(uid: uid, reservedUsers: workoutClass.reservedUsers ?? [])
if isReserved {
print("user is already in class so therefore can't be added again")
return nil
} else {
transaction.updateData(["reservationCnt": increment, "reservedUsers": addUser], forDocument: classReference)
return nil
}
} completion: { object, error in
if let error = error {
print(error.localizedDescription)
self.isDisabled = false
} else {
print("Successfully ran transaction with object: \(object ?? "")")
self.isDisabled = false
}
}
}
func removeReservation(documentId: String, reservedUserDetails: [String: Any], uid: String){
let decrementValue: Int64 = -1
let decrement = FieldValue.increment(decrementValue)
let removeUser = FieldValue.arrayRemove([reservedUserDetails])
let classReference = db.document("schedules/Redwood City/dates/\(self.stateDate.dbDateFormat)/classes/\(documentId)")
db.runTransaction { transaction, errorPointer in
let classDocument: DocumentSnapshot
do {
print("Getting classDocument for docId: \(documentId) in addReservedUser()")
try classDocument = transaction.getDocument(classReference)
} catch let fetchError as NSError {
errorPointer?.pointee = fetchError
return nil
}
guard let workoutClass = try? classDocument.data(as: WorkoutClass.self) else {
let error = NSError(
domain: "AppErrorDomain",
code: -3,
userInfo: [
NSLocalizedDescriptionKey: "Unable to retrieve reservedUsers from snapshot \(classDocument)"
]
)
errorPointer?.pointee = error
return nil
}
let isReserved = self.checkIsReserved(uid: uid, reservedUsers: workoutClass.reservedUsers ?? [] )
if isReserved {
transaction.updateData(["reservationCnt": decrement, "reservedUsers": removeUser], forDocument: classReference)
return nil
} else {
print("user not in class so therefore can't be removed")
return nil
}
} completion: { object, error in
if let error = error {
print(error.localizedDescription)
self.isDisabled = false
} else {
print("Successfully ran removeReservation transaction with object: \(object ?? "")")
self.isDisabled = false
}
}
}
func checkIsReserved(uid: String, reservedUsers: [reservedUser]) -> Bool {
return reservedUsers.contains { $0.uid == uid }
}
}
App screenshot
Reservation button is the green/grey button at the bottom of the view
As this is a race condition, You have already acknowledged the use of Transactions for the update which is the most desirable as this can ensure the update is successful before allowing the App to change button status.
I.e. by using a transaction and only updating the UI Button state on success, which is explained here
The recommendation is to keep the state of the button mapped to what is in the document, therefore you are likely to exceed rate limits by updating the same field continuously based on the flipping of the button.
Another way to handle this tracking of the state of enrollment is to add a new document that indicates the state of the enrollment for the user to a collection that is the class they are enrolling in.
I.e. Rather than having the class user enrolling into being a document, make that a collection and each time the enrollment state changes, write a new document. This will allow for updates to occur without using transactions and the current state of enrollments is contained within the latest document. This latest document can be read and used as the status of the button within the App with the added benefit that the state will always update to the status contained within Firestore.
I ended up resolving this by adding a disable check conditional before the conditional that decides whether to show the "Reserve" or "Cancel" button.
This way when my Firestore transaction is running the user will see a spinner instead and can't monkey test the button. The spinner helps to show that the reservation operation is in progress. When the transaction hits its completion block I disable the isDisabled Bool and the listener is in sync (the user then sees the newly toggled button state)
if workoutClassVM.isDisabled {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: Color("bruinGreenColor")))
} else if checkIsReserved(uid: authVM.user?.uid ?? "", reservedUsers: workoutClass.reservedUsers ?? []) {
...
I am trying to add a nested struct to Firestore and for some reason the contents added are all non-structs, which look something like:
The structs look something like this:
type Status struct {
Title string `json:"title,omitempty" firestore:"title,omitempty"`
Message string `json:"message,omitempty" firestore:"title,omitempty"`
}
type Config struct {
Name string `json:"name,omitempty" firestore:"name,omitempty"`
Status Status `json:"status,omitempty" firestore:"status,omitempty"`
}
And the code looks something like this:
import (
"context"
firebase "firebase.google.com/go/v4"
"google.golang.org/api/option"
)
func main() {
configuration := Config{
Name: "Test",
Status: Status{
Title: "hello",
Message: "hi",
},
}
ctx := context.Background()
config := firebase.Config{
ProjectID: "",
StorageBucket: "",
}
opt := option.WithCredentialsFile("firebase_config.json")
app, err := firebase.NewApp(ctx, &config, opt)
if err != nil {
panic(err)
}
// Get an auth client from the firebase.App
client, err := app.Firestore(ctx)
if err != nil {
panic(err)
}
_, _, err = client.Collection("forecast").Add(ctx, configuration)
if err != nil {
panic(err)
}
}
The above code only works for elements that are not in the nested structure.
Any help on this would be appreciated
Update 1
Status is not a sub collection but an object, something like:
{
"name": "Test",
"status": {
"title": "hello",
"message": "hi"
}
}
Firestore is optimized as a hash entry and retrieval datastore. As a consequence, it's better to create maps out of your structs. Structs are good for Go data modeling but when it's time to submit to the database, convert it to a map.
I usually just use Fatih's struct to map converter
It makes it easy to reason about your data on the Go side and still be able to submit it for storage.
Posting this as Community Wiki answer, based in the discussion of the comments.
The solution for this case seems to be adding values manually in a field of type Map. The steps to achieve that are the following: Access the Firebase console -> Firestore -> Create a document -> Add field of type Map. Following this order it's possible to create a field of type Map, that has the format needed to add data as described in the description of the question.
More information about this type, how it works, sort options, etc., can be found in the official documentation here: Supported data types.
I have 2 entities with One to Many relationship in my Mikro-Orm (v3.6.15) model (connected to Postgresql - pg v8.3.0):
Uygulama (Parent)
#Entity({ tableName: "Uygulamalar", collection: "Uygulamalar" })
export class Uygulama {
#PrimaryKey({ fieldName: "Id" })
id!: number;
#Property({ fieldName: "Adi" })
adi!: string;
#Property({ fieldName: "Kod" })
kod!: string;
#Property({ fieldName: "UygulamaSahibi" })
uygulamaSahibi!: string;
#Property({ fieldName: "createdAt" })
createdAt = new Date();
#Property({ fieldName: "updatedAt", onUpdate: () => new Date() })
updatedAt = new Date();
#OneToMany({ entity: () => Modul, mappedBy: "rootUygulama", cascade: [] })
moduller = new Collection<Modul>(this);
}
Modul (Child)
export class Modul {
#PrimaryKey({ fieldName: "Id" })
id!: number;
#Property({ fieldName: "Adi" })
adi!: string;
#Property({ fieldName: "Kod" })
kod!: string;
#Property({ fieldName: "createdAt" })
createdAt = new Date();
#Property({ fieldName: "updatedAt", onUpdate: () => new Date() })
updatedAt = new Date();
#ManyToOne({ entity: () => Uygulama, joinColumn: "UygulamaId", cascade: [],})
rootUygulama!: Uygulama;
#OneToMany({ entity: () => Ekran, mappedBy: "rootModul", orphanRemoval: true,})
ekranlar = new Collection<Ekran>(this);
}
I have a rest endpoint (Expressjs) to create Modul object from posted Http request body :
router.post("/", async (req, res) => {
const modul = DI.modulRepository.create(req.body);
await DI.modulRepository.persistAndFlush(modul);
res.send(modul);
});
When I try to post JSON object below to create a new Modul (rootUygulama object is already in the database):
{
"adi": "Deneme Modülü 3",
"kod": "DM1",
"rootUygulama": {
"id": 66,
"adi": "Deneme Uygulaması",
"kod": "DU",
"uygulamaSahibi": "xxxxxx",
"createdAt": "2020-07-24T21:18:47.874Z",
"updatedAt": "2020-07-24T21:18:47.874Z",
"moduller": [
]
}
}
I get error :
[query] insert into "Uygulamalar" ("Adi", "Id", "Kod", "UygulamaSahibi", "createdAt", "updatedAt") values ('Deneme Uygulaması', 66, 'DU', 'szengin', '2020-07-25 00:18:47.874', '2020-07-25 00:18:47.874') returning "Id" [took 6 ms]
node_modules/mikro-orm/dist/utils/Logger.js:22
[query] rollback
node_modules/mikro-orm/dist/utils/Logger.js:22
(node:14344) UnhandledPromiseRejectionWarning: error: insert into "Uygulamalar" ("Adi", "Id", "Kod", "UygulamaSahibi", "createdAt", "updatedAt") values ($1, $2, $3, $4, $5, $6) returning "Id" - cannot insert into column "Id"
at Parser.parseErrorMessage (d:\NODEJS\BildirimYonetimi\backend\node_modules\pg-protocol\dist\parser.js:278:15)
at Parser.handlePacket (d:\NODEJS\BildirimYonetimi\backend\node_modules\pg-protocol\dist\parser.js:126:29)
at Parser.parse (d:\NODEJS\BildirimYonetimi\backend\node_modules\pg-protocol\dist\parser.js:39:38)
at Socket.<anonymous> (d:\NODEJS\BildirimYonetimi\backend\node_modules\pg-protocol\dist\index.js:8:42)
at Socket.emit (events.js:311:20)
at Socket.EventEmitter.emit (domain.js:482:12)
at addChunk (_stream_readable.js:294:12)
at readableAddChunk (_stream_readable.js:275:11)
at Socket.Readable.push (_stream_readable.js:209:10)
at TCP.onStreamRead (internal/stream_base_commons.js:186:23)
<node_internals>/internal/process/warning.js:32
(node:14344) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
<node_internals>/internal/process/warning.js:32
(node:14344) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
When I send JSON as below object is created and inserted to database successfuly
{
"adi": "Deneme Modülü 3",
"kod": "DM1",
"rootUygulama": 66
}
Even if I set empty cascade array to relationship attribute repository tries to insert parent object and fails.
Am I missing something in configuration?
Edit:
For my Typescript client how will I define rootUygulama property?
export interface Modul {
id: number;
adi: string;
kod: string;
createdAt: Date;
updatedAt: Date;
rootUygulama: Uygulama;
ekranlar: Array<Ekran>;
}
Should it be like
rootUygulama: Uygulama | number;
This is not about cascading, the behaviour is correct. When you pass just the PK, it is considered as existing entity, if you pass an object, it is considered as new entity. It's not about the PK being set or not, if you want that behaviour, you need to program it yourself.
MikroORM works based on change set tracking, so only managed objects (entities loaded from database) can produce update queries. If you want to fire update query for the rootUygulama object, then use em.nativeUpdate(). If you want to still send the payload with it as object, but you care just about the PK, you could also explicitly merge that entity to the EM (that way it becomes managed, just like if you have loaded it from the db).
const modul = DI.modulRepository.create(req.body);
// if we see PK, we merge, so it won't be considered as new object
if (modul.rootUygulama.id) {
DI.em.merge(modul.rootUygulama);
// here we could also fire the `em.nativeUpdate(Uygulama, modul.rootUygulama);`
// to fire an update query, but then you should use `em.transactional()` to have that update query inside the same TX as the flush
}
await DI.modulRepository.persistAndFlush(modul);
res.send(modul);
Btw I would strongly suggest not to disable cascading unless you truly understand what you are doing, as the defaults are to cascade merge and persist, which you usually want/need.
I have the following query to Cloud Kit
func cloudKitManageUserPublicTable (typeOfOperation: Int, encriptedBabyName: String?, encriptedOllyKey:String?, result: (error: NSError?, userHasBeenFound: Bool?, ollYKeyHasBeenFound: Bool?, encriptedBabyNameFound: String?) -> Void){
// OPERATION TYPES
// 1 - search for user and add, link or update a key
// 2 - search for user and check he has a key
// 3 - search for a key
// 4 - search for user and add a default one if has not been found
print("cloudKitManageUserPublicTable - operation \(typeOfOperation)")
var recordCounter = 0
var publicUserRecord = CKRecord(recordType: "PublicUsers")
let useriCloudID = NSUserDefaults.standardUserDefaults().objectForKey("useriCloudID") as! String
var predicate = NSPredicate()
switch typeOfOperation {
case 1:
predicate = NSPredicate(format: "useriCloudID == %#", useriCloudID)
case 2:
predicate = NSPredicate(format: "useriCloudID == %#", useriCloudID)
case 3:
predicate = NSPredicate(format: "encriptedOllyKey == %#", encriptedOllyKey!)
default:
print("no scenarios")
}
let cloudKitQuery = CKQuery(recordType: "PublicUsers", predicate: predicate)
let queryOperation = CKQueryOperation(query: cloudKitQuery)
let operationQueue = NSOperationQueue()
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase;
queryOperation.database = publicDatabase
queryOperation.recordFetchedBlock = { (record : CKRecord) -> Void in
publicUserRecord = record
recordCounter += 1
}
queryOperation.queryCompletionBlock = { (cursor: CKQueryCursor?, error: NSError?) -> Void in
print("cloudKitManageUserPublicTable - # of record found - \(recordCounter)")
if error != nil
{
// ERROR STOP
print("cloudKitManageUserPublicTable - error - \(error)")
result(error: error, userHasBeenFound: nil, ollYKeyHasBeenFound: nil, encriptedBabyNameFound: nil)
}
else
{
switch typeOfOperation {
case 1:
// KEY FOUND, UPDATE
print("cloudKitManageUserPublicTable - search for user and add or update a key")
publicUserRecord["encriptedBabyName"] = encriptedBabyName!
publicUserRecord["encriptedOllyKey"] = encriptedOllyKey!
publicUserRecord["hasKey"] = true
publicDatabase.saveRecord(publicUserRecord) { (CKRecord: CKRecord?, error: NSError?) -> Void in
if error != nil
{
print("cloudKitManageUserPublicTable - creating key - UPDATE error \(error)")
result(error: error, userHasBeenFound: nil, ollYKeyHasBeenFound: nil, encriptedBabyNameFound: nil)
}
else
{
print("cloudKitManageUserPublicTable - creating key - UPDATE OK")
result(error: error, userHasBeenFound: nil, ollYKeyHasBeenFound: true, encriptedBabyNameFound: nil)
}
}
case 2:
print("cloudKitManageUserPublicTable - search for user and check it has a key")
if publicUserRecord.objectForKey("hasKey") as? Bool == false
{
print("cloudKitManageUserPublicTable - user do not have a key")
result(error: nil, userHasBeenFound: nil, ollYKeyHasBeenFound: false, encriptedBabyNameFound: nil)
}
else
{
print("cloudKitManageUserPublicTable - user has a key")
result(error: nil, userHasBeenFound: nil, ollYKeyHasBeenFound: true, encriptedBabyNameFound: nil)
}
case 3:
if recordCounter == 0
{
print("cloudKitManageUserPublicTable - no record has this key")
result(error: nil, userHasBeenFound: nil, ollYKeyHasBeenFound: false, encriptedBabyNameFound: nil)
}
else
{
print("cloudKitManageUserPublicTable - \(recordCounter) records have this key")
result(error: nil, userHasBeenFound: nil, ollYKeyHasBeenFound: true, encriptedBabyNameFound: nil)
}
case 4:
if recordCounter == 0
{
// NO USER FOUND, CREATE
print("cloudKitManageUserPublicTable - search for user and add a default one if has not been found")
// publicUserRecord["encriptedBabyName"] = ""
// publicUserRecord["encriptedOllyKey"] = ""
publicUserRecord["hasKey"] = false
publicUserRecord["useriCloudID"] = useriCloudID
publicDatabase.saveRecord(publicUserRecord) { (CKRecord: CKRecord?, error: NSError?) -> Void in
dispatch_async(dispatch_get_main_queue()) {
if error != nil
{
print("cloudKitManageUserPublicTable - no user - CREATE error \(error)")
result(error: nil, userHasBeenFound: false, ollYKeyHasBeenFound: nil, encriptedBabyNameFound: nil)
}
else
{
print("cloudKitManageUserPublicTable - no user found - CREATE ok")
result(error: nil, userHasBeenFound: false, ollYKeyHasBeenFound: nil, encriptedBabyNameFound: nil)
}
}
}
}
else
{
// USER FOUND - DO NOTHING
print("cloudKitManageUserPublicTable - user exists, do nothing for now")
result(error: nil, userHasBeenFound: true, ollYKeyHasBeenFound: nil, encriptedBabyNameFound: nil)
}
default:
print("no scenarios")
}
}
}
operationQueue.addOperation(queryOperation)
}
The method above is called for the FIRST TIME by the method bellow where I wait for the completion handler to return and then call the method above for the SECOND TIME to then verify if the publicUserRecord["hasKey"] = false was set to false.
But my problem is. When I call the method the SECOND TIME to verify the publicUserRecord["hasKey"] it returns that nothing has been saved yet. If I then wait a bit and call the method to verify the publicUserRecord["hasKey"] for the THIRD TIME then it finds and verifies it.
As I am doing the very same call and the results have been saved in the FIRST call seems that theres a bit of a lag with apple servers or am I not using the completion handlers, dispatch_async, dispatch_sync properly? Any ideas?
func manageUserPublicTable(){
tryAgainButtonOutlet.enabled = false
let spinningActivity = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
spinningActivity.labelText = "Talking to Apple Servers"
spinningActivity.detailsLabelText = "Creating user credentials..."
cloudKitManageUserPublicTable(1) { (error, userExists) -> Void in
dispatch_sync(dispatch_get_main_queue()) {
if error != nil
{
spinningActivity.hide(true)
// let user try again
let optionMenu = UIAlertController(title: nil, message: "Are you all set to upload this record?!", preferredStyle: .ActionSheet)
let tryAgain = UIAlertAction(title: "Try again", style: .Default, handler: {
(alert: UIAlertAction!) -> Void in
self.tryAgainButtonOutlet.enabled = true
})
let cancelAction = UIAlertAction(title: "Not yet...", style: .Cancel, handler: {
(alert: UIAlertAction!) -> Void in
self.tryAgainButtonOutlet.enabled = true
})
optionMenu.addAction(tryAgain)
optionMenu.addAction(cancelAction)
if(isIPad()) {
optionMenu.popoverPresentationController!.permittedArrowDirections = UIPopoverArrowDirection()
optionMenu.popoverPresentationController!.sourceView = self.view
optionMenu.popoverPresentationController!.sourceRect = CGRectMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0, 1.0, 1.0)
}
self.presentViewController(optionMenu, animated: true, completion: nil)
}
else
{
spinningActivity.hide(true)
self.manageEncriptedKey()
}
}
}
}
Indeed there can be some time between saving data and being able to retrieve it. There is no specification how long this could be. Usually it's less than seconds.
So you have to make your app logic so that it does not care about that. You already saved the record so your app already knows the content of that record. You could pass on the record from the callback so that you don't have to query for it again.
One other think. Your functions are a bit large to my taste. There is too much functionality in 1 function. That makes it difficult to read. Ideally a function should do only one thing.
I am willing to create a sample http.Response instance in golang with a sample body string.
Problem is, its body property accepts ReadCloser instance. But as its a dummy response instance, I was wondering if there is some trick to set it easily without setting up all that stream read/close parts.
As suggested by Not_a_Golfer and JimB:
io.ReadCloser is an interface that is satisfied when a struct implements both the Read and the Close functions.
Fortunately, there is ioutil.NopCloser, which takes a io.Reader and wraps it in the nopCloser struct, which implements both Read and Close. However, its Close function does nothing as implied from the name.
Here is an example:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
t := http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString("Hello World")),
}
buff := bytes.NewBuffer(nil)
t.Write(buff)
fmt.Println(buff)
}
To play with the code, click here.
Further to the top answer, I have found that in order for the response to be treated as the genuine article by clients, it needs to be more fully formed. For a normal (200) response, I do the following:
body := "Hello world"
t := &http.Response{
Status: "200 OK",
StatusCode: 200,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
ContentLength: int64(len(body)),
Request: req,
Header: make(http.Header, 0),
}
Then you can, for example, add headers (with a 401 status code, to ask for authorisation, say). req is the http.Request for which you are generating the response.
This should work..
func main(){
go serveHTTP(*port, *host)
select {}
}
func serveHTTP(port int, host string) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
requestHandler(w, r)
})
addr := fmt.Sprintf("%v:%d", host, port)
server := &http.Server {
Addr: addr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
err := server.ListenAndServe()
log.Println(err.Error())
}
func requestHandler(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, `Success!`)
}
Yes, the ioutil.NopCloser is just what I needed!
Am trying to test a method that performs calls to the facebook API (via a helper function) for a social-connect endpoint, and I want to mock the facebook response coming from the helper function, so my solution is like follows:
Expected facebook response (converted to my own UserData struct) is:
UserData {
ID: facebookID,
Email: email,
FirstName: firstName,
LastName: lastName,
}
So I create the expected response like this:
fbUserData, _ := json.Marshal(UserData{
ID: facebookID,
Email: email,
FirstName: firstName,
LastName: lastName,
})
fbUserDataResponse := &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(string(fbUserData))),
}
Then I can mock the response for the method calling the facebook API like this:
s.fbGateway.EXPECT().ExecuteGetQuery(userUrl).Return(fbUserDataResponse, nil).Times(1)
The point here is that this is really about mocking any kind of functions that return *http.Response data (in my case I am calling the facebook API via a helper function that returns the http Response, as mentioned above).