Design pattern question

Hi group,

I wonder whether you could give me any advice on how to structure some code?

Its pretty simple really. I want to be able to call an API like this:

SourceControl.checkout
SourceControl.update

Etc

I want to be able to plugin different source control systems that conform to the API.

My initial thought was just to go with a simple C++ style wrapper/delegate approach:

module SourceControl

def self.checkout(repository, dest, options={})
  @sourceControl.checkout(repository, dest, options)
end

def self.update(path, options={})
  @sourceControl.update(parth, options)
end

class SourceControlAPI

  def checkout(repository, dest, options={})
  end

  def update(path, options={})
  end

end

end

But I bet there is a nicer way, perhaps without having to duplicate the API?

Any pointers appreciated.

Cheers,
James

Hi,

I wonder whether you could give me any advice on how to structure some code?
Its pretty simple really. I want to be able to call an API like this:

SourceControl.checkout

SourceControl.update

Maybe this one gives some good pointers..

hope it helps,
saji

···

--

Saji N Hameed,
ARC-ENV, Center for Advanced Information Science and Technology,
University of Aizu, Tsuruga, Ikki-machi,
Aizuwakamatsu-shi, Fukushima 965-8580,
Japan

Tel: +81242 37-2736
Fax:+81242 37-2760
email: saji@u-aizu.ac.jp
url: http://www.u-aizu.ac.jp
bib: Web of Science

James French wrote in post #1055131:

Its pretty simple really. I want to be able to call an API like this:

SourceControl.checkout
SourceControl.update

You better do

sc.checkout
sc.update

i.e. not use a constant but an instance variable.

I want to be able to plugin different source control systems that
conform to the API.

Well, in Ruby there are no interfaces so you can, but do not need to use
inheritance.

My initial thought was just to go with a simple C++ style
wrapper/delegate approach:

module SourceControl

def self.checkout(repository, dest, options={})
  @sourceControl.checkout(repository, dest, options)
end

def self.update(path, options={})
  @sourceControl.update(parth, options)
end

class SourceControlAPI

  def checkout(repository, dest, options={})
  end

  def update(path, options={})
  end

end

end

But I bet there is a nicer way, perhaps without having to duplicate the
API?

Especially, what advantages does it have?

I'd probably simply do

class CVS
  def checkout(dest, options={})
  end

  def update(path, options={})
  end
end

class SVN
  def checkout(dest, options={})
  end

  def update(path, options={})
  end
end

class Perforce
  def checkout(dest, options={})
  end

  def update(path, options={})
  end
end

and then

# connection
sc = SVN.new("svn-repo.my.network.com", "James", "p@ssword")

# repository
sc.checkout("repo", "/tmp/work") do |repo|
  repo.update("foo")
  File.open(repo.path + "foo", "a") {|io| io.write("a change)}
  repo.submit("foo")
end # auto commit

sc.close # needed?

You could even make SVN.new accept a block which receives "sc" and
cleans up in ensure regardless how the block terminates.

http://blog.rubybestpractices.com/posts/rklemme/002_Writing_Block_Methods.html

Kind regards

robert

···

--
Posted via http://www.ruby-forum.com/\.

Hi,

I wonder whether you could give me any advice on how to structure some code?
Its pretty simple really. I want to be able to call an API like this:

SourceControl.checkout

SourceControl.update

Maybe this one gives some good pointers..

hope it helps,
saji

Thanks for the pointer - I'll take a look at that.

···

-----Original Message-----
From: saji.uaizu@gmail.com [mailto:saji.uaizu@gmail.com] On Behalf Of Saji Hameed
Sent: 05 April 2012 14:17
To: ruby-talk ML
Subject: Re: Design pattern question

James French wrote in post #1055131:

Its pretty simple really. I want to be able to call an API like this:

SourceControl.checkout
SourceControl.update

You better do

sc.checkout
sc.update

i.e. not use a constant but an instance variable.

I want to be able to plugin different source control systems that
conform to the API.

Well, in Ruby there are no interfaces so you can, but do not need to use inheritance.

My initial thought was just to go with a simple C++ style
wrapper/delegate approach:

module SourceControl

def self.checkout(repository, dest, options={})
  @sourceControl.checkout(repository, dest, options) end

def self.update(path, options={})
  @sourceControl.update(parth, options) end

class SourceControlAPI

  def checkout(repository, dest, options={})
  end

  def update(path, options={})
  end

end

end

But I bet there is a nicer way, perhaps without having to duplicate
the API?

Especially, what advantages does it have?

I'd probably simply do

class CVS
  def checkout(dest, options={})
  end

  def update(path, options={})
  end
end

class SVN
  def checkout(dest, options={})
  end

  def update(path, options={})
  end
end

class Perforce
  def checkout(dest, options={})
  end

  def update(path, options={})
  end
end

and then

# connection
sc = SVN.new("svn-repo.my.network.com", "James", "p@ssword")

# repository
sc.checkout("repo", "/tmp/work") do |repo|
  repo.update("foo")
  File.open(repo.path + "foo", "a") {|io| io.write("a change)}
  repo.submit("foo")
end # auto commit

sc.close # needed?

You could even make SVN.new accept a block which receives "sc" and cleans up in ensure regardless how the block terminates.

http://blog.rubybestpractices.com/posts/rklemme/002_Writing_Block_Methods.html

Kind regards

robert

Using the Jay Fields link and slightly modifying I've got something I'm happy with. Thanks for the help Robert, as always :slight_smile:

module SourceControl
  def self.init(subject)
    subject.public_methods(false).each do |meth|
      (class << self; self; end).class_eval do
        define_method meth do |*args|
          subject.send meth, *args
        end
      end
    end
  end
end

class SourceControlAPI
  
  def checkout(repository, dest, options={})
  end
  
  def update(path, options={})
  end
  
end

class SVN < SourceControlAPI
  
  def checkout(repository, dest, options={})
    puts "Checking out from SVN"
  end
  
  def update(path, options={})
  end
  
end

SourceControl.init(SVN.new)
SourceControl.checkout('foo', 'bar')

···

-----Original Message-----
From: Robert Klemme [mailto:lists@ruby-forum.com]
Sent: 05 April 2012 14:20
To: ruby-talk ML
Subject: Re: Design pattern question

Using the Jay Fields link and slightly modifying I've got something I'm happy with. Thanks for the help Robert, as always :slight_smile:

module SourceControl
def self.init(subject)
subject.public_methods(false).each do |meth|
(class << self; self; end).class_eval do
define_method meth do |*args|
subject.send meth, *args
end
end
end
end
end

That's just a mechanism to generate delegation. And you redefine
methods all the time. I don't think meta programming is necessary at
all here.

class SourceControlAPI

def checkout(repository, dest, options={})
end

def update(path, options={})
end

Those are not really needed. And if you want to have them then I'd
make them either contain a default implementation if there exists one
or throw an exception which explains that this method must be
implemented for the class.

end

class SVN < SourceControlAPI

def checkout(repository, dest, options={})
puts "Checking out from SVN"
end

def update(path, options={})
end

end

SourceControl.init(SVN.new)
SourceControl.checkout('foo', 'bar')

You are hiding specific state in a constant. I don't like that
because it will prevent accessing multiple different source control
systems in the same program or from multiple threads. I find that
requirement to be able to invoke all the methods through the constant
leads to a situation where you severely cripple modularity and
reusability without really gaining something.

Kind regards

robert

···

On Thu, Apr 5, 2012 at 4:25 PM, James French <James.French@naturalmotion.com> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Using the Jay Fields link and slightly modifying I've got something
I'm happy with. Thanks for the help Robert, as always :slight_smile:

module SourceControl
def self.init(subject)
subject.public_methods(false).each do |meth|
(class << self; self; end).class_eval do
define_method meth do |*args|
subject.send meth, *args
end
end
end
end
end

That's just a mechanism to generate delegation. And you redefine methods all the time. I don't think meta programming is necessary at all here.

class SourceControlAPI

def checkout(repository, dest, options={})
end

def update(path, options={})
end

Those are not really needed. And if you want to have them then I'd make them either contain a default implementation if there exists one or throw an exception which explains that this method must be implemented for the class.

True, but I want to document the API somewhere central. I raise exceptions saying the method must be implement.

end

class SVN < SourceControlAPI

def checkout(repository, dest, options={})
puts "Checking out from SVN"
end

def update(path, options={})
end

end

SourceControl.init(SVN.new)
SourceControl.checkout('foo', 'bar')

You are hiding specific state in a constant. I don't like that because it will prevent accessing multiple different source control systems in the same program or from multiple threads. I find that requirement to be able to invoke all the methods through the constant leads to a situation where you severely cripple modularity and reusability without really gaining something.

Also true, if I wanted to do that, but I don't. I just need to initialise it at startup and use the source control system that was automatically determined from then on, without knowing which one it is. I think this API is working really well for me... I also just like the API of SourceControl.checkout etc.

Kind regards

robert

···

-----Original Message-----
From: Robert Klemme [mailto:shortcutter@googlemail.com]
Sent: 05 April 2012 16:08
To: ruby-talk ML
Subject: Re: Design pattern question
On Thu, Apr 5, 2012 at 4:25 PM, James French <James.French@naturalmotion.com> wrote:

--
remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/

True, but I want to document the API somewhere central. I raise exceptions saying the method must be implement.

That's not very ruby-like at all.

Yes it is, in that it is good design. Otherwise, why would we have NotImplementedError? We use it all the time in our projects that implement an abstract interface:

graph/dev/lib/dep_analyzer.rb: raise NotImplementedError, "subclass responsibility"
graph/dev/lib/dep_analyzer.rb: raise NotImplementedError, "subclass responsibility"
graph/dev/lib/dep_analyzer.rb: raise NotImplementedError, "subclass responsibility"

···

On Apr 5, 2012, at 09:19 , Steve Klabnik wrote:

True, but I want to document the API somewhere central. I raise exceptions saying the method must be implement.

That's not very ruby-like at all.

We'll have to agree to disagree on this one, Ryan. Abstract classes
are pretty silly in Ruby. Then again, I find subclassing distasteful
in general, so this might just be a style/preference thing.

W dniu 5 kwietnia 2012 21:27 użytkownik Steve Klabnik
<steve@steveklabnik.com> napisał:

We'll have to agree to disagree on this one, Ryan. Abstract classes

> are pretty silly in Ruby. Then again, I find subclassing distasteful
> in general, so this might just be a style/preference thing.

Abstract classes are sometimes useful (although rarely). For example,
how else could you do the kind of database adapter handling Sequel[1]
does?

[1] sequel | RubyGems.org | your community gem host

-- Matma Rex

Abstract classes are sometimes useful (although rarely). For example,
how else could you do the kind of database adapter handling Sequel[1]
does?

Use duck typing.

This:

class Abstract
  def foo
    throw NotImplementedException
  end
end

class Foo < Abstract
  def foo
    puts "in foo!"
  end
end

class Bar < Abstract
  def foo
    puts "in bar's foo"
  end
end

Is the exact same thing as

class Foo
  def foo
    puts "in foo!"
  end
end

class Bar
  def foo
    puts "in bar's foo"
  end
end

in fact...

class Baz
end

Baz.new.foo # => NoMethodError

class Baz < Abstract
end

Baz.new.foo # => NotImplementedError

Almost the exact same thing. Just a different exception.

Sequel provides a lot of helper methods and aliases in the abstract
class. Just look thru the docs of Sequel::Database (this is the
abstract class) and it's list of methods - you only need to implement
a handful, the rest are helpers.

-- Matma Rex

Insert a 'def bar; puts "bar"; end' into my Abstract class, the end
point is still the same. Adding an empty method that throws a slightly
different error doesn't really do anything worthwhile in my opinion.
If it makes you happy, then do it.

Adding an empty method that throws a slightly
different error doesn't really do anything worthwhile in my opinion.

Adding an empty method that throws a slightly
different error doesn't really do anything worthwhile in my opinion.

Because being explicit about the interactions across classes isn't worthwhile? Having an explicit place to document the contract isn't worthwhile? Sorry... I'm not buying it. From your example [with my edits]:

class Abstract
[# contract doco]
def foo
   [raise] NotImplemented[Error][, "helpful explanation--much more so than NoMethodError"]
end
end

Now I know where I can document the foo contract and suggest proper ways to implement it. You lose that when you throw that out the window and go with duck typing.

That reminds me... How do you suggest a beginner know the difference between a typo and an unimplemented ducktyped method interface?

···

On Apr 5, 2012, at 15:05 , Steve Klabnik wrote:
On Apr 5, 2012, at 15:05 , Steve Klabnik wrote: