On Fri, Dec 11, 2009 at 9:03 AM, Matt Wynne <[email protected]> wrote: > Yes we did, briefly - probably before the discussion went online on the > lighthouse ticket. I think there were a couple of reasons why not REST: > (1) simplicity - we wanted the wire server to be as low-tech as possible so > it would be easy and lightweight to implement on any platform (think > Cuke4EmbeddedDevice). Everyone's got a TCP stack but not everyone's got a > simple web server like sinatra - I'm not even sure there's an especially > simple one for .NET > (2) flexibility - as the protocol has emerged, it's become clear that the > relationship is pretty much client / server, but when we first started we > weren't sure how much chatter would need to go back & forth. Obviously a > REST server can't start making requests back to a client (unless that client > also starts offering a web server), but if you've got two peers talking over > a socket you can have two-way comms. > >> If anyone has any thoughts on this, I'd be interested. > > If you're using your protocol to effectively get remote control over > objects, have you considered writing something to support DRB[1] on the > server side? Otherwise you could also look at SLIM[2], which is the protocol > Fitnesse uses in much the same way as Cucumber uses the wire protocol. We > did consider using slim in Cucumber - it's widely supported with server > implementations on many platforms, but since SLIM has much more flexibility > that we needed, we decided to keep things simple.
Thanks, Matt. [2] has some really good ideas and some that I've already thought of. I kinda like the multiple instructions per transaction. I don't know if I need that yet. I considered [1] but wasn't sure how easy it would be to write a server for DRb in another language. All I'm doing is representing the Ruby object protocol (object.send :message, args) in REST. JSON suffices for just about any object serialization and you can customize it for your class. I currently don't need to layer any command structure on top of JSON. Only the message's arguments payload and return values get serialized/deserialized. JSON is built-in or supported via a standard lib or module in all of my targeted languages. .Net/PHP/Perl and Python. REST works to supply the basics of the command layer. Resources are classes, objects and bare functions. E.g. To create a new object from a class. obj=MyObject.new => POST: http://server/class/MyObject obj-MyObject.new(1,2) POST: http://server/class/MyObject 'args=[1,2]' These return an object id along with json_cerate args. To call a method on said object: obj.f(:a=>10) => PUT : http://server/object/1/msg/f 'args={"a":1,"b":2}' .. and returning results as above. To call a class level method, return a single object or a list of them, use GET. obj=MyClass.get(1) => GET http://server/class/MyClass/msg/get?args=1 -- raw method -- obj =JSON.parse GET http://server/object/1 List (via a query): array = MyClass.query('where active = ?', true) => GET http://server/class/MyClass/msg/query?'args=["WHERE active = ?",true]' # with URL encoding -- raw list of all objects of a class -- GET http://server/class/MyClass/* -- raw list of all objects -- GET http://server/object/* ... and so on All returned objects on the Ruby side are just handles that only hold the id. They don't contain any marshaled data from the target language side. The reasoning is that all variable access must be done via accessor methods since that is the case in Ruby. However, given a REST call like this in a target server: PUT http://server/object/2/msg/a the implementation is free to decide that that is a variable access, and assuming it is public, return its value. Setting the contents of a variable is just the same, but with Ruby conventions that the target language has to obey: obj.a=1 => PUT http://server/object/3/a= 'args=[1]' The motivation behind this is to use both Cuke and RSpec to test legacy (or BDD develop new) projects w/o having to install and learn another BDD/xUnit framework. I think this suffices for all RSpec use cases, but it may be incomplete. If you are anyone think of anything I have missed, let me know. Already you can: sudo gem install <sexy name goes here> (in ../spec_helper.rb: require '<sexy name>') describe MyObject before(:each) @obj = MyObject.new end it "should be valid" @obj.should be_valid # would PUT/ object/<id>msg/valid? end it "should have x == ['avg', 10.0]" do @obj.x.should == ['avg', 10.0] end end Note there is no need to declare a class on the RSpec side, or to include a module or inherit from some other class. In PHP to get the specs to pass: --- my_object.php -- <a naming convention> class MyObject { var x; function MyObject() {x=array("avg", 10.0);} function isValid() { return true; } // another convention } You get in to a R-G-R rhythm just as quickly as with native Ruby code. The server code for this would need to reflect on MyObject to determine variable access and discover methods that start with 'isXXX' Currently this is very primitive. There is no support for passing objects to other objects. I don't know about class level methods in all target languages yet. I thought the user could provide class factory classes for those if they don't exist, like in PHP4. It requires an object store on the server side, each implementation is free to decide on that. In Sinatra I it is just a class variable for now, the object ids are the indexes. >> Should we : >> o continue to use web proxies for objects and method calls? >> o -or- switch to running the matchers (should ==, etc.) over the wire >> protocol, more like cucumber? > > I'm not really clear what the goal of your app is, but to go more into line > with what cucumber's doing, I think what you'd do would be to allow RSpec to > remotely invoke entire examples ("it blocks") remotely. You could then write > those examples entirely in the native language. I mentioned the goal above. Writing those examples in the target language is not what I had in mind. I only want code in the actual SUT to be written in that SUT's language. You'd have to port ALL of RSpec's matchers there or use another framework's assertion lib. It'd be interesting but __way__ beyond the scope of what I am doing. Also, I am not sure what an it block would look like it it were sent over the wire. I imagine it queries the SUT wire server for any matching "it"s and any left over are just reported as unimplemented. BTW, does Cuke's wire protocol allow for a mixture of Ruby side and SUT side step definitions? >> BTW, anyone working on a Cuke4PHP? > > That's the first time I've heard anyone even suggest it :) I'd be happy to > collaborate with someone if they wanted some help with that. I seriously need this. And am willing to help. Currently we are using Cuke+Webrat+Mechanize to test only the web facing surface of the app. But to get things like 'Given I have a user named "Ted" with password "Secret"' to work and insert into the legacy DB is tricky. If that step could execute on the PHP side, life would be sweet! AFAIK, there seems to be simple REST servers in at least .Net and PHP. Fitzgerald for PHP and just using IIS and writing handlers in .Net. Ed > > [1]http://segment7.net/projects/ruby/drb/introduction.html > [2]http://fitnesse.org/FitNesse.UserGuide.SliM > Ed Howland http://greenprogrammer.wordpress.com http://twitter.com/ed_howland _______________________________________________ rspec-users mailing list [email protected] http://rubyforge.org/mailman/listinfo/rspec-users
