有没有人将谷歌文档集成到他们的 iOS 应用程序中?通过示例代码,Google Docs 的 API 比我预期的要复杂得多,而且示例都是 MacOS。是的,有 iOS 支持,但明显缺乏关于如何使用它的示例代码,而且文档也有些缺乏。
我确实在网上找到了一个接口(interface)类,但它是基于旧的、已弃用的 Google Docs API 版本,并且不能使用 XCode 4.2 进行编译。
我所追求的是一个相对直接的界面,它允许:
我已经开始编写这样的界面,但到目前为止,它的复杂程度超出了我的预期。如果有人有任何建议或可以指导我的示例,我将不胜感激。
我的偏好是包装器是操作系统中立的;这意味着我希望能够在 MacOS 和 iOS 中使用相同的界面。同样,这就是我开始写的内容,但我不禁觉得我必须在这里重新发明轮子。
谢谢
最佳答案
好吧,在没有其他人回答的情况下,我硬着头皮自己写了一个包装器。
我现在拥有适用于 Mac OS 和 iOS 的单一包装器,可显着简化与 Google 文档的界面。
下面是实际界面的所有代码。我应该指出这个类作为一个单例,你需要通过更新行来为每个项目稍微定制它:
#define GOOGLE_DATA_CLIENT_ID @"<client id>.apps.googleusercontent.com"
#define GOOGLE_DATA_SECRET @"<google data secret>"
#define GOOGLE_DATA_USERNAME @"googleDocsUsername"
#define GOOGLE_DATA_PASSWORD @"googleDocsPassword"
使用您从 Google 获得的适当值。
我还指出该类通过 NSUserDefaults 存储密码,并使用单独的实用程序类以加密方式执行此操作。我在 bitbucket 中创建了一个存储库,而不是用所有这些额外的代码来阻塞这个答案:
https://bitbucket.org/pkclsoft/gdatainterface
其中包含构建两个目标的整个 XCode 项目,一个用于 Mac OS,一个用于 iOS。我在应用程序商店中的一个应用程序中使用了这两个应用程序,效果很好。可能是虽然这个项目是为我构建的,但您必须根据自己的目的对其进行调整。该项目包含一整套使用我的代码构建和运行的 Google SDK。我包含它是为了尝试降低与新版本 SDK 不兼容的风险,以防任何人使用它。
这是目前的接口(interface)规范:
//
// GDataInterface.h
// GDataInterface
//
// Some of the code in this class is from the original GData sample code, but it has been
// enhanced somewhat and made to work on both iOS and MacOS transparently.
//
// Created by Peter Easdown on 19/12/11.
// Copyright (c) 2011 PKCLsoft. All rights reserved.
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#import "GDataDocs.h"
#import <UIKit/UIKit.h>
#else
#import "GData/GData.h"
#endif
@interface GDataInterfaceTypes
// This handler is used by methods that have no explicit result. The boolean value indicates
// the success or failure of the the methods action.
//
typedef void (^CompletionHandler)(BOOL successful);
// This handler is called to update a progress indicator as a file is uploaded.
//
typedef void (^UploadProgressHandler)(double min, double max, double value);
// This handler is called to update a progress indicator as a file is downloaded.
//
typedef void (^DownloadProgressHandler)(double min, double max, double value);
@end
@interface GDataInterface : NSObject {
#if TARGET_OS_IPHONE
// Needed so that when authenticating under iOS, we can push the google authentication
// view, and later pop it.
//
UIViewController *rootController_;
#endif
GDataFeedDocList *mDocListFeed;
GDataServiceTicket *mDocListFetchTicket;
NSError *mDocListFetchError;
GDataFeedDocRevision *mRevisionFeed;
GDataServiceTicket *mRevisionFetchTicket;
NSError *mRevisionFetchError;
GDataEntryDocListMetadata *mMetadataEntry;
GDataServiceTicket *mUploadTicket;
id uploadWindow;
CompletionHandler uploadCompletionHandler;
NSString *username_;
NSString *password_;
}
// This handler is used when a list of documents has been requested. The results parameter
// will be nil if the request failed. If successful, then it will contain an array of
// GDataEntryDocBase objects.
//
typedef void (^RetrievalCompletionHandler)(GDataFeedDocList* results, BOOL successful);
// This handler is used when a document has been downloaded. If something prevented the
// download from succeeding, then error parameter will be non-nil.
//
typedef void (^DocumentDownloadCompletionHandler)(NSData* fileContents, BOOL successful);
// Initializer that provides the username and password.
//
- (id) initWithUsername:(NSString*)username andPassword:(NSString*)password;
// Returns the shared instance of the class. There will only ever be a single instance
// of this class.
//
+ (GDataInterface*) sharedInstance;
// Returns YES if currently signed in.
//
- (BOOL) isSignedIn;
// Signs in or out depending on current state, and executes the options completion handler
// block. The window parameter is used to specify the root viewController object used when
// displaying login windows via GData, or error dialogs.
//
- (void) signInOrOutWithCompletionHandler:(CompletionHandler)handler forWindow:(id)window;
// Will retrieve a list of documents using the cached connection, and call the specified
// handler block, providing the list of documents, and a success/fail indication.
//
- (void) retrieveDocumentListWithCompletionHandler:(RetrievalCompletionHandler)handler;
// Will download the file at the specified URL. This is not Google Docs specific and will work
// for any URL. Be careful not to try and retrieve large files and the result is stored
// in memory.
//
- (void) downloadURL:(NSURL*)url withProgressHandler:(DownloadProgressHandler)progressHandler andCompletionHandler:(DocumentDownloadCompletionHandler)handler;
// Will download the specified google docs document.
//
- (void) downloadDocument:(GDataEntryDocBase*)document withProgressHandler:(DownloadProgressHandler)progressHandler andCompletionHandler:(DocumentDownloadCompletionHandler)handler;
// Uploads the document entry, optionally updating it with a new revision.
//
- (void) uploadEntry:(GDataEntryDocBase*)docEntry asNewRevision:(BOOL)newRevision forWindow:(id)window withProgressHandler:(UploadProgressHandler)progressHandler andCompletionHandler:(CompletionHandler)handler;
// Uploads the specified file to the authenticated google docs account.
//
- (void)uploadFileAtPath:(NSString *)path forWindow:(id)window withProgressHandler:(UploadProgressHandler)progressHandler andCompletionHandler:(CompletionHandler)handler;
// More for internal use than anything else. Used to determine the mime type based on the google docs class
// and/or file extension.
//
- (void)getMIMEType:(NSString **)mimeType andEntryClass:(Class *)class forExtension:(NSString *)extension;
// Getter and Setter for username,
//
- (void) setUsername:(NSString*)newUsername;
- (NSString*) username;
// Getter and Setter for password. The password will be encrypted before storing it in user preferances.
//
- (void) setPassword:(NSString*)newPassword;
- (NSString*) password;
// Returns the username that google is given for signing in.
//
- (NSString *)signedInUsername;
// Returns a static instance of the docs service.
//
+ (GDataServiceGoogleDocs *)docsService;
@end
下面是实现:
//
// GDataInterface.m
// GDataInterface
//
// Created by Peter Easdown on 19/12/11.
// Copyright (c) 2011 PKCLsoft. All rights reserved.
//
#import "GDataInterface.h"
#import "Util.h"
#if TARGET_OS_IPHONE
#import "GTMOAuth2ViewControllerTouch.h"
#import "GData.h"
#else
#import "GData/GTMOAuth2WindowController.h"
//#import "GDataServiceGoogleSpreadsheet.h"
#endif
#define GOOGLE_DATA_CLIENT_ID @"<client id>.apps.googleusercontent.com"
#define GOOGLE_DATA_SECRET @"<google data secret>"
#define GOOGLE_DATA_USERNAME @"googleDocsUsername"
#define GOOGLE_DATA_PASSWORD @"googleDocsPassword"
@interface GDataInterface (PrivateMethods)
- (GDataServiceTicket *) uploadTicket;
- (void) setUploadTicket:(GDataServiceTicket *)ticket;
- (GDataFeedDocList *)docListFeed;
- (void)setDocListFeed:(GDataFeedDocList *)feed;
- (NSError *)docListFetchError;
- (void)setDocListFetchError:(NSError *)error;
- (GDataServiceTicket *)docListFetchTicket;
- (void)setDocListFetchTicket:(GDataServiceTicket *)ticket;
@end
@implementation GDataInterface
static NSString *const kKeychainItemName = @"GDataInterface: Google Docs";
// Initializer that provides the username and password.
//
- (id) initWithUsername:(NSString*)username andPassword:(NSString*)password {
self = [super init];
if (self != nil) {
username_ = [username retain];
password_ = [password retain];
[[GDataInterface docsService] setUserCredentialsWithUsername:username_ password:password_];
}
return self;
}
- (void) setUsername:(NSString*)newUsername {
username_ = [newUsername retain];
[[GDataInterface docsService] setUserCredentialsWithUsername:newUsername password:password_];
}
- (NSString*) username {
return username_;
}
- (void) setPassword:(NSString*)newPassword {
password_ = [newPassword retain];
[[GDataInterface docsService] setUserCredentialsWithUsername:username_ password:newPassword];
[Util setPassword:newPassword forKey:GOOGLE_DATA_PASSWORD];
}
- (NSString*) password {
return password_;
}
static GDataInterface *shared_instance_;
// Returns the shared instance of the class. There will only ever be a single instance
// of this class.
//
+ (GDataInterface*) sharedInstance {
if (shared_instance_ == nil) {
shared_instance_ = [[GDataInterface alloc] initWithUsername:[[NSUserDefaults standardUserDefaults] valueForKey:GOOGLE_DATA_USERNAME] andPassword:[Util getPassword:GOOGLE_DATA_PASSWORD]];
// Load the OAuth token from the keychain, if it was previously saved
NSString *clientID = GOOGLE_DATA_CLIENT_ID;
NSString *clientSecret = GOOGLE_DATA_SECRET;
GTMOAuth2Authentication *auth;
#if TARGET_OS_IPHONE
auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName clientID:clientID clientSecret:clientSecret];
#else
auth = [GTMOAuth2WindowController authForGoogleFromKeychainForName:kKeychainItemName
clientID:clientID
clientSecret:clientSecret];
#endif
[[GDataInterface docsService] setAuthorizer:auth];
}
return shared_instance_;
}
- (NSString *)signedInUsername {
// Get the email address of the signed-in user
GTMOAuth2Authentication *auth = [[GDataInterface docsService] authorizer];
BOOL isSignedIn = auth.canAuthorize;
if (isSignedIn) {
return auth.userEmail;
} else {
return nil;
}
}
- (BOOL) isSignedIn {
return ([self signedInUsername] != nil);
}
- (void)runSigninThenInvokeHandler:(CompletionHandler)handler forWindow:(id)window {
// Applications should have client ID and client secret strings
// hardcoded into the source, but the sample application asks the
// developer for the strings
NSString *clientID = GOOGLE_DATA_CLIENT_ID;
NSString *clientSecret = GOOGLE_DATA_SECRET;
// Show the OAuth 2 sign-in controller
NSString *scope = [GTMOAuth2Authentication scopeWithStrings:
[GDataServiceGoogleDocs authorizationScope],
[GDataServiceGoogleSpreadsheet authorizationScope],
nil];
#if TARGET_OS_IPHONE
NSAssert((window != nil), @"window must be a non-nil navigation controller");
GTMOAuth2ViewControllerTouch *viewController;
viewController = [GTMOAuth2ViewControllerTouch
controllerWithScope:scope
clientID:clientID
clientSecret:clientSecret
keychainItemName:kKeychainItemName
completionHandler:^(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error) {
[rootController_ dismissModalViewControllerAnimated:YES];
[rootController_ release];
rootController_ = nil;
// callback
if (error == nil) {
[[GDataInterface docsService] setAuthorizer:auth];
username_ = [self signedInUsername];
handler(YES);
} else {
NSLog(@"Authentication error: %@", error);
NSData *responseData = [[error userInfo] objectForKey:@"data"]; // kGTMHTTPFetcherStatusDataKey
if ([responseData length] > 0) {
// show the body of the server's authentication failure response
NSString *str = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"%@", str);
}
handler(NO);
}
}];
// Optional: display some html briefly before the sign-in page loads
NSString *html = @"<html><body bgcolor=silver><div align=center>Loading sign-in page...</div></body></html>";
viewController.initialHTMLString = html;
// For iOS, window is a navigation controller.
//
rootController_ = [(UIViewController*)window retain];
[rootController_ presentModalViewController:viewController animated:YES];
#else
NSBundle *frameworkBundle = [NSBundle bundleForClass:[GTMOAuth2WindowController class]];
GTMOAuth2WindowController *windowController;
windowController = [GTMOAuth2WindowController controllerWithScope:scope
clientID:clientID
clientSecret:clientSecret
keychainItemName:kKeychainItemName
resourceBundle:frameworkBundle];
[windowController signInSheetModalForWindow:window
completionHandler:^(GTMOAuth2Authentication *auth, NSError *error) {
// callback
if (error == nil) {
[[GDataInterface docsService] setAuthorizer:auth];
username_ = [auth userEmail];
handler(YES);
} else {
handler(NO);
}
}];
#endif
}
- (void) signInOrOutWithCompletionHandler:(CompletionHandler)handler forWindow:(id)window {
if (![self isSignedIn]) {
// Sign in
[self runSigninThenInvokeHandler:handler forWindow:window];
} else {
// Sign out
GDataServiceGoogleDocs *service = [GDataInterface docsService];
#if TARGET_OS_IPHONE
[GTMOAuth2ViewControllerTouch removeAuthFromKeychainForName:kKeychainItemName];
#else
[GTMOAuth2WindowController removeAuthFromKeychainForName:kKeychainItemName];
#endif
[service setAuthorizer:nil];
handler(YES);
}
}
- (void) retrieveDocumentListWithCompletionHandler:(RetrievalCompletionHandler)handler {
[self setDocListFeed:nil];
[self setDocListFetchError:nil];
[self setDocListFetchTicket:nil];
GDataServiceGoogleDocs *service = [GDataInterface docsService];
GDataServiceTicket *ticket;
// Fetching a feed gives us 25 responses by default. We need to use
// the feed's "next" link to get any more responses. If we want more than 25
// at a time, instead of calling fetchDocsFeedWithURL, we can create a
// GDataQueryDocs object, as shown here.
NSURL *feedURL = [GDataServiceGoogleDocs docsFeedURL];
GDataQueryDocs *query = [GDataQueryDocs documentQueryWithFeedURL:feedURL];
[query setMaxResults:1000];
[query setShouldShowFolders:NO];
ticket = [service fetchFeedWithQuery:query
completionHandler:^(GDataServiceTicket *ticket, GDataFeedBase *feed, NSError *error) {
// callback
[self setDocListFeed:(GDataFeedDocList *)feed];
[self setDocListFetchError:error];
[self setDocListFetchTicket:nil];
if (handler != nil) {
handler((GDataFeedDocList *)feed, (error == nil));
}
}];
[self setDocListFetchTicket:ticket];
}
- (void) downloadDocument:(GDataEntryDocBase*)document withProgressHandler:(DownloadProgressHandler)progressHandler andCompletionHandler:(DocumentDownloadCompletionHandler)handler {
// the content src attribute is used for downloading
NSURL *exportURL = [[document content] sourceURL];
if (exportURL != nil) {
GDataQuery *query = [GDataQuery queryWithFeedURL:exportURL];
[query addCustomParameterWithName:@"exportFormat"
value:@"txt"];
NSURL *downloadURL = [query URL];
// Read the document's contents asynchronously from the network
// requestForURL:ETag:httpMethod: sets the user agent header of the
// request and, when using ClientLogin, adds the authorization header
NSURLRequest *request = [[GDataInterface docsService] requestForURL:downloadURL
ETag:nil
httpMethod:nil];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
[fetcher setAuthorizer:[[GDataInterface docsService] authorizer]];
__block double maxSize = 10240.0;
if (progressHandler != nil) {
[fetcher setReceivedDataBlock:^(NSData *dataReceivedSoFar) {
if ([[fetcher response] expectedContentLength] > 0) {
maxSize = [[fetcher response] expectedContentLength];
} else if ([dataReceivedSoFar length] > maxSize) {
maxSize += 5120.0;
}
progressHandler(0.0, maxSize, (double)[dataReceivedSoFar length]);
}];
}
[fetcher setCommentWithFormat:@"downloading \"%@\"", [[document title] stringValue]];
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
if (progressHandler != nil) {
// Update the progress handler with a "complete" progress.
//
progressHandler(0.0, (double)[data length], (double)[data length]);
}
// callback
if (error == nil) {
// Successfully downloaded the document
//
if (handler != nil) {
handler(data, YES);
}
} else {
if (handler != nil) {
handler(nil, NO);
}
}
}];
}
}
- (void) downloadURL:(NSURL*)url withProgressHandler:(DownloadProgressHandler)progressHandler andCompletionHandler:(DocumentDownloadCompletionHandler)handler {
NSURL *downloadURL = [url copy];
// Read the document's contents asynchronously from the network
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
__block double maxSize = 10240.0;
if (progressHandler != nil) {
[fetcher setReceivedDataBlock:^(NSData *dataReceivedSoFar) {
if ([[fetcher response] expectedContentLength] > 0) {
maxSize = [[fetcher response] expectedContentLength];
} else if ([dataReceivedSoFar length] > maxSize) {
maxSize += 5120.0;
}
progressHandler(0.0, maxSize, (double)[dataReceivedSoFar length]);
}];
}
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
if (progressHandler != nil) {
progressHandler(0.0, (double)[data length], (double)[data length]);
}
// callback
if (error == nil) {
// Successfully downloaded the document
//
if (handler != nil) {
handler(data, YES);
}
} else {
if (handler != nil) {
handler(nil, NO);
}
}
}];
// Block, waiting for 60 seconds for the download.
//
[fetcher waitForCompletionWithTimeout:60.0];
if ([fetcher isFetching] == YES) {
// OK, so this looks like we've timed out waiting for the download to complete. Cancel the
// fetch.
//
[fetcher stopFetching];
if (handler != nil) {
handler(nil, NO);
}
}
}
#pragma mark Upload
- (void)getMIMEType:(NSString **)mimeType andEntryClass:(Class *)class forExtension:(NSString *)extension {
// Mac OS X's UTI database doesn't know MIME types for .doc and .xls
// so GDataEntryBase's MIMETypeForFileAtPath method isn't helpful here
struct MapEntry {
NSString *extension;
NSString *mimeType;
NSString *className;
};
static struct MapEntry sMap[] = {
{ @"csv", @"text/csv", @"GDataEntryStandardDoc" },
{ @"doc", @"application/msword", @"GDataEntryStandardDoc" },
{ @"docx", @"application/vnd.openxmlformats-officedocument.wordprocessingml.document", @"GDataEntryStandardDoc" },
{ @"ods", @"application/vnd.oasis.opendocument.spreadsheet", @"GDataEntrySpreadsheetDoc" },
{ @"odt", @"application/vnd.oasis.opendocument.text", @"GDataEntryStandardDoc" },
{ @"pps", @"application/vnd.ms-powerpoint", @"GDataEntryPresentationDoc" },
{ @"ppt", @"application/vnd.ms-powerpoint", @"GDataEntryPresentationDoc" },
{ @"rtf", @"application/rtf", @"GDataEntryStandardDoc" },
{ @"sxw", @"application/vnd.sun.xml.writer", @"GDataEntryStandardDoc" },
{ @"txt", @"text/plain", @"GDataEntryStandardDoc" },
{ @"xls", @"application/vnd.ms-excel", @"GDataEntrySpreadsheetDoc" },
{ @"xlsx", @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", @"GDataEntrySpreadsheetDoc" },
{ @"jpg", @"image/jpeg", @"GDataEntryStandardDoc" },
{ @"jpeg", @"image/jpeg", @"GDataEntryStandardDoc" },
{ @"png", @"image/png", @"GDataEntryStandardDoc" },
{ @"bmp", @"image/bmp", @"GDataEntryStandardDoc" },
{ @"gif", @"image/gif", @"GDataEntryStandardDoc" },
{ @"html", @"text/html", @"GDataEntryStandardDoc" },
{ @"htm", @"text/html", @"GDataEntryStandardDoc" },
{ @"tsv", @"text/tab-separated-values", @"GDataEntryStandardDoc" },
{ @"tab", @"text/tab-separated-values", @"GDataEntryStandardDoc" },
{ @"pdf", @"application/pdf", @"GDataEntryPDFDoc" },
{ nil, nil, nil }
};
NSString *lowerExtn = [extension lowercaseString];
for (int idx = 0; sMap[idx].extension != nil; idx++) {
if ([lowerExtn isEqual:sMap[idx].extension]) {
*mimeType = sMap[idx].mimeType;
*class = NSClassFromString(sMap[idx].className);
return;
}
}
*mimeType = nil;
*class = nil;
return;
}
- (void) uploadEntry:(GDataEntryDocBase*)docEntry asNewRevision:(BOOL)newRevision forWindow:(id)window withProgressHandler:(UploadProgressHandler)progressHandler andCompletionHandler:(CompletionHandler)handler {
uploadWindow = [window retain];
uploadCompletionHandler = [handler copy];
NSURL *uploadURL;
if (newRevision == YES) {
GDataQueryDocs *query = [GDataQueryDocs queryWithFeedURL:[[docEntry
uploadEditLink] URL]];
[query setShouldCreateNewRevision:YES];
uploadURL = [query URL];
} else {
uploadURL = [GDataServiceGoogleDocs docsUploadURL];
}
// make service tickets call back into our upload progress selector
GDataServiceGoogleDocs *service = [GDataInterface docsService];
[service setServiceUploadProgressHandler:^(GDataServiceTicketBase *ticket, unsigned long long numberOfBytesRead, unsigned long long dataLength) {
if (progressHandler != nil) {
progressHandler(0.0, (double)dataLength, (double)numberOfBytesRead);
}
}];
// insert the entry into the docList feed
//
// to update (replace) an existing entry by uploading a new file,
// use the fetchEntryByUpdatingEntry:forEntryURL: with the URL from
// the entry's uploadEditLink
GDataServiceTicket *ticket;
if (newRevision == YES) {
ticket = [service fetchEntryByUpdatingEntry:docEntry
forEntryURL:uploadURL
delegate:self
didFinishSelector:@selector(uploadFileTicket:finishedWithEntry:error:)];
} else {
ticket = [service fetchEntryByInsertingEntry:docEntry
forFeedURL:uploadURL
delegate:self
didFinishSelector:@selector(uploadFileTicket:finishedWithEntry:error:)];
}
[ticket setUploadProgressHandler:^(GDataServiceTicketBase *ticket, unsigned long long numberOfBytesRead, unsigned long long dataLength) {
// progress callback
if (progressHandler != nil) {
progressHandler(0.0, (double)dataLength, (double)numberOfBytesRead);
}
}];
// we turned automatic retry on when we allocated the service, but we
// could also turn it on just for this ticket
[self setUploadTicket:ticket];
[service setServiceUploadProgressHandler:nil];
}
- (void)uploadFileAtPath:(NSString *)path forWindow:(id)window withProgressHandler:(UploadProgressHandler)progressHandler andCompletionHandler:(CompletionHandler)handler {
NSString *errorMsg = nil;
// make a new entry for the file
NSString *mimeType = nil;
Class entryClass = nil;
NSString *extn = [path pathExtension];
[self getMIMEType:&mimeType andEntryClass:&entryClass forExtension:extn];
if (!mimeType) {
// for other file types, see if we can get the type from the Mac OS
// and use a generic file document entry class
mimeType = [GDataUtilities MIMETypeForFileAtPath:path
defaultMIMEType:nil];
entryClass = [GDataEntryFileDoc class];
}
if (mimeType && entryClass) {
GDataEntryDocBase *newEntry = [entryClass documentEnt
关于objective-c - 用于 Google 文档的 iOS Objective-C 包装器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8675662/
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby1.9+ 关于ruby-主要:Objectwhenrun
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您
当我使用has_one时,它工作得很好,但在has_many上却不行。在这里您可以看到object_id不同,因为它运行了另一个SQL来再次获取它。ruby-1.9.2-p290:001>e=Employee.create(name:'rafael',active:false)ruby-1.9.2-p290:002>b=Badge.create(number:1,employee:e)ruby-1.9.2-p290:003>a=Address.create(street:"123MarketSt",city:"SanDiego",employee:e)ruby-1.9.2-p290
matlab打开matlab,用最简单的imread方法读取一个图像clcclearimg_h=imread('hua.jpg');返回一个数组(矩阵),往往是a*b*cunit8类型解释一下这个三维数组的意思,行数、数和层数,unit8:指数据类型,无符号八位整形,可理解为0~2^8的数三个层数分别代表RGB三个通道图像rgb最常用的是24-位实现方法,即RGB每个通道有256色阶(2^8)。基于这样的24-位RGB模型的色彩空间可以表现256×256×256≈1670万色当imshow传入了一个二维数组,它将以灰度方式绘制;可以把图像拆分为rgb三层,可以以灰度的方式观察它figure(1
我正在尝试为我的iOS应用程序设置cocoapods但是当我执行命令时:sudogemupdate--system我收到错误消息:当前已安装最新版本。中止。当我进入cocoapods的下一步时:sudogeminstallcocoapods我在MacOS10.8.5上遇到错误:ERROR:Errorinstallingcocoapods:cocoapods-trunkrequiresRubyversion>=2.0.0.我在MacOS10.9.4上尝试了同样的操作,但出现错误:ERROR:Couldnotfindavalidgem'cocoapods'(>=0),hereiswhy:U
我正在使用带有Rails的Devise,我想添加一个方法“getAllComments”,所以我这样写:classUser在我的Controller中:defdashboard@user=current_user@comments=@user.getAllComments();end当我访问我的url时,我得到了undefinedmethod`getAllComments'for#我做错了什么?谢谢 最佳答案 因为getAllComments是一个类方法,而您正试图将其作为实例方法访问。您要么需要访问它:User.getAllCom