Class that opens and adds variables to instances of another class?


(Leam Hall) #21

1) All your classes output JSON, which is nice, but you don't show any classes that read that JSON, and I'm pretty certain that at that point you will find you want a single object, a Person model, to read in the JSON and represent it to the rest of the code. (And if you are going to do that later, you might as well do that now?)

Check out "class Crew" near the end. It takes in JSON and sets attributes based on values. In theory it could be more complex code, but that's the basics.

2a) If all your person objects get decorated with Appearance and Cadet, then there is no point using Decorator Pattern. You might as well put it all in one class. Or use some other way to split up the code, which I agree might be a good idea if that is your intent. (Method Classes still seem the way to go, for me, in that eventuality.)

Not all will, and there are lots of cases where there will be very minimal details. For example, something like this (mostly pseudo code.)

stranger = Person.new
"You get a message from #{stranger.name}. #{stranger.gender == 'M' ? 'He' : 'She'} seems to know a lot about what you're doing."

2b) If only some of the person objects get decorated, then you will never be able to call `person.rank` safely because you won't know if the object has a rank method or not. If this isn't a problem because you never plan to use these objects once they have built JSON for you, then you might as well just have a class to build JSON for you, and not represent person as an object at all. Which brings us back to (1).

Each program needs certain data about the instance. An instance of Crew needs rank and skills, so it would pull in the JSON and test for those things. If they did not exist, use the shared Character_Tools module to generate them. Each program only needs to ensure what it needs is there.

TL;DR: My recommendation would be: Design the object that needs to read and represent all this JSON about a person first. Call _that_ the Person class, and work from there. It might clarify your thinking, and probably make for much shorter code.

That was the original design but it kept growing into a mess. Using at hoc modules seems to clean it up.

Leam

···

On 07/27/2018 06:54 AM, Andy Jones wrote:

Hope that helps...

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam Hall
Sent: 27 July 2018 11:19
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another class?

Hey Andy, this is my pre-workday proof of concept. Lots of ways to
improve it, I'm sure. Instead of a formal DB connection I'm using JSON
and passing it around. The last bit takes in the JSON and uses just what
it needs. In reality, the code would add a career to the Person if there
wasn't already one. I just wanted to see if I could pass the data around
well.

Where there is a "# blah...blah" after a puts or a to_s, that's the output.

Does this answer the questions you raised?

Thanks!

Leam

#####

class Person
    require 'json'
    def initialize()
      @name = 'Al'
      @age = 14
      @gender = 'Female'
    end
    def to_s
      puts "#{@name} is a #{@age} year old #{@gender}."
    end
    def to_j
      @record = {:name => @name, :age => @age, :gender => @gender}
      @record.to_json
    end
end

al = Person.new
al.to_s # Al is a 14 year old Female.
puts al.to_j # {"name":"Al","age":14,"gender":"Female"}

module Appearance
    def hair
      @hair = 'Raider cut dishwater blond'
    end
    def frame
      @frame = 'skinny'
    end
    def appearance_update
      hair
      frame
      app_record = {:hair => @hair, :frame => @frame}
      @record = @record.merge(app_record)
      @record.to_json
    end
end

al.extend Appearance
al.appearance_update

module Cadet
    def rank
      @rank = 'Cadet Sergeant'
    end
    def age
      @age += 1
    end
    def career_to_s
      rank
      age
      puts "#{@name} is a #{@age} year old #{@gender} #{@rank}."
    end
    def career_to_j
      @career_record = {:age => @age, :rank => @rank}
      @record = @record.merge(@career_record)
      @record.to_json
    end
end

al.extend Cadet
al.career_to_s # Al is a 15 year old Female Cadet Sergeant.
al_json = al.career_to_j

puts al_json #
{"name":"Al","age":15,"gender":"Female","hair":"Raider cut dishwater
blond","frame":"skinny","rank":"Cadet Sergeant"}

class Crew
    require 'json'
    def initialize(data)
      @data = JSON.parse(data, :symbolize_names => true)
      @name = @data[:name]
      @rank = @data[:rank]
    end
    def to_s
      puts "Welcome aboard, #{@rank} #{@name}!"
    end
end
my_crew = Crew.new(al_json)
my_crew.to_s # Welcome aboard, Cadet Sergeant Al!

#####

On 07/24/2018 06:08 AM, Andy Jones wrote:

Well, your UDS will have to put the player data in an object of some class. If not Player, then what?

Put another way, when an object "pulls data from the UDS" -- where does it hold it?

Generally speaking when reading from a data store you want one of more "models" -- objects that represent the data to the rest of the application. It's fine if you don't want Player to be the model, but whatever "models" player information to the rest of the application will need that rank attribute (and all the others from all those decorators) or you will have the same problem.

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam Hall
Sent: 24 July 2018 10:58
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another class?

Understood. Here's how I see that being dealt with.

1. There is a Universal Data Store (UDS) of some unrestricted format.
JSON, MOngoDB, or whatever. Each program pulls from the same data store.

2. The character generate program creates basic characters.

3. The game program:
     For a new game, generates crew. Thus uses the basic character
generation program and adds specific stuff required by the game.
     For a saved game, those characters are pulled from the UDS.

4. The fiction tracker program:
     For new characters:
       Does basic character generation
       Adds data like appearance, mental workup, last known location, etc.
     Otherwise pulls the data from the UDS.
     Allows manual edits of data.
     Allows creation of new data fields.

5. The relationship tracker feeds data into Neo4J.
     A basic character from #2 has no relational data.
     A game character from #3 would have the rest of the crew as
relationships.
     A fiction character might overload Neo4J, if you've seen how
convoluted my fiction gets...

In each case the programs include the modules they need. The UDS has
*all* the data, but each program only uses parts of it. The relationship
tracker doesn't care about physical appearance, etc.

By keeping things in modules, each program can use the modules it needs.
Does that solve the problems you are seeing?

I really appreciate the feedback, you are helping clarify my thinking.

Leam

On 07/23/2018 03:26 AM, Andy Jones wrote:

Again, I'm dubious that this is the best approach, because when you get an unknown method error half way into your main, game code, and it turns out that that's because you called person.rank on a Person that doesn't have the Career_Tools mixin, that is going to be painful to debug.

Alternatively you will have to pepper your code with things like `if defined?(person.rank); rank_thing(person.rank); end` which will make the code much harder to follow and ALL the bugs harder to find.

The way I would do it, personally? I know that some Persons have a rank. So I put rank right there in the Person class. Civilians have a rank of nil. Nil values are a PITA but considerably less so than missing methods! So now I can at least do `rank_thing(person.rank)` and I guess rank_thing starts with `return unless rank`...

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam
Hall
Sent: 21 July 2018 1:19 am
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another
class?

And this is how I spend my Friday night...

Thoughts? In this case Career_Tools has to account for anything in any
of the careers. Most of the time those things are pretty simple.

####

class Person
      def initialize(data)
        @name = data[:name]
      end
      def name
        @name
      end
end

module Career_Tools
      def rank
        @rank
      end
      def rank=(career)
        @rank = career.ranks
      end
end

module Relationships
      def friends
        @friends
      end
      def friends=(l)
        if @friends.nil?
          @friends = Array.new
        end
        @friends << l
      end
end

class Cadet
      def ranks
        ['Cadet', 'Cadet Corporal', 'Cadet Sergeant', 'Cadet
Leiutenant'].sample
      end
end

### main
person_data = {:name => 'Al'}
al = Person.new(person_data)
puts al.name
al.extend Career_Tools
al.extend Relationships
al.friends = "CC One"
al.friends = "Wilbur"
puts al.friends.join(", ")
career = Cadet.new
al.rank=(career)
puts al.rank

####

On 07/20/2018 03:48 PM, leam hall wrote:

This will be part of the weekend's joy.

There are lots of roles, and most instances of Person get at least one.
A few instances get more than one, but which role a particular instance
gets varies widely.

More questions as I stumble through...

Leam

On Thu, Jul 19, 2018 at 6:12 AM, Andy Jones <Andy.Jones@jameshall.co.uk >>>>> <mailto:Andy.Jones@jameshall.co.uk>> wrote:

       You have to ask yourself: composition or mixin? The easiest way is
       with a mixin, but it has limitations.

       ~~~~
       module Engineer
          def can_do_engines?; true; end
       end

       class Person
          def initialize(name); @name = name; end
       end

       p = Person.new("fred")
       p.extend Engineer
       puts p.can_do_engines?
       ~~~~

       Note that you decorate an object --- not a class. If you want to
       add to the functionality of every instance of a class, I'm not sure
       that counts as Decorator pattern?

       If you want to do it with Composition, instead, have a look at the
       Ruby documentation for Forwardable. (Sorry this is a bit rushed;
       chaotic today...)

       I think that this might be the article that originally clued me into
       Decorator? Not sure:
       https://robots.thoughtbot.com/evaluating-alternative-decorator-

implementations-in

       <https://robots.thoughtbot.com/evaluating-alternative-decorator-

implementations-in>

       -----Original Message-----
       From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org
       <mailto:ruby-talk-bounces@ruby-lang.org>] On Behalf Of Leam Hall
       Sent: 19 July 2018 10:59
       To: Ruby users
       Subject: Class that opens and adds variables to instances of another
       class?

       I think I'm trying to figure out the Decorator pattern. What I want is
       to have a <thing> that takes an instance of <something> and changes it.
       The changes can include modifying and adding instance variables.

       Haven't quite figured it out yet, thoughts?

       Thanks!

       Leam

       Here's the error:
       ###
       Traceback (most recent call last):
       3: from test_decorator.rb:28:in `<main>'
       2: from test_decorator.rb:28:in `new'
       1: from test_decorator.rb:17:in `initialize'
       test_decorator.rb:20:in `assign_role': undefined method `role=' for
       #<Person:0x0000562e9a870978 @data={:name=>"Al"}> (NoMethodError)
       ###

       Code:
       ###
           1 class Person
           2 def initialize(data)
           3 @data = data
           4 end
           5 def name
           6 @data[:name] || "Fred"
           7 end
           8 end
           9
          10 class Role
          11 class Person
          12 attr_accessor :role
          13 end
          14
          15 def initialize(data)
          16 @person = data[:person]
          17 assign_role(data[:role])
          18 end
          19 def assign_role(role)
          20 @person.role = role
          21 end
          22 end
          23
          24 data = {:name => 'Al'}
          25
          26 al = Person.new(data)
          27 role_data = {:person => al, :role => 'Cadet'}
          28 Role.new(role_data)
          29 puts al.name <http://al.name>
          30 #al.role = "Cadet Sergeant"
       ###

       Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org
       <mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>>

       Click here to view Company Information and Confidentiality
       Notice.<http://www.jameshall.co.uk/index.php/small-print/email-

disclaimer

       <http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>>

       Please note that we have updated our privacy policy in line with new
       data protection regulations. Please refer to our website to view the
       ways in which we handle your data.

       Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org
       <mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>


(Leam Hall) #22

Ah...I may be understanding. Slowly. Let me see if I have it.

1. There is a file called 'person.rb' that sets the basic Person class.
  class Person
    def initialize(data)
      @name = data[:name]
    end
  end

2. There is a program called "Starship" that uses 'crew.rb', a file that opens the Person class.
  class Person
    attr_reader :rank
    def setup_crew(data)
      @rank = data[:rank]
    end
  end

3. Starship looks something like this:
  # Create data from UDS pull.
  data = {:name => 'Al', :rank => 'Captain'}

  al = Person.new(data)
  require 'crew'
  al.setup_crew(data)
  puts al.rank # "Captain" She was promoted. :slight_smile:

Is that what you're suggesting? I can pull the data from the UDS, put it into a hash, and then send it to each "setup_" method.

Leam

···

On 07/27/2018 06:54 AM, Andy Jones wrote:

1) All your classes output JSON, which is nice, but you don't show any classes that read that JSON, and I'm pretty certain that at that point you will find you want a single object, a Person model, to read in the JSON and represent it to the rest of the code. (And if you are going to do that later, you might as well do that now?)

2a) If all your person objects get decorated with Appearance and Cadet, then there is no point using Decorator Pattern. You might as well put it all in one class. Or use some other way to split up the code, which I agree might be a good idea if that is your intent. (Method Classes still seem the way to go, for me, in that eventuality.)

2b) If only some of the person objects get decorated, then you will never be able to call `person.rank` safely because you won't know if the object has a rank method or not. If this isn't a problem because you never plan to use these objects once they have built JSON for you, then you might as well just have a class to build JSON for you, and not represent person as an object at all. Which brings us back to (1).

TL;DR: My recommendation would be: Design the object that needs to read and represent all this JSON about a person first. Call _that_ the Person class, and work from there. It might clarify your thinking, and probably make for much shorter code.

Hope that helps...

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam Hall
Sent: 27 July 2018 11:19
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another class?

Hey Andy, this is my pre-workday proof of concept. Lots of ways to
improve it, I'm sure. Instead of a formal DB connection I'm using JSON
and passing it around. The last bit takes in the JSON and uses just what
it needs. In reality, the code would add a career to the Person if there
wasn't already one. I just wanted to see if I could pass the data around
well.

Where there is a "# blah...blah" after a puts or a to_s, that's the output.

Does this answer the questions you raised?

Thanks!

Leam

#####

class Person
    require 'json'
    def initialize()
      @name = 'Al'
      @age = 14
      @gender = 'Female'
    end
    def to_s
      puts "#{@name} is a #{@age} year old #{@gender}."
    end
    def to_j
      @record = {:name => @name, :age => @age, :gender => @gender}
      @record.to_json
    end
end

al = Person.new
al.to_s # Al is a 14 year old Female.
puts al.to_j # {"name":"Al","age":14,"gender":"Female"}

module Appearance
    def hair
      @hair = 'Raider cut dishwater blond'
    end
    def frame
      @frame = 'skinny'
    end
    def appearance_update
      hair
      frame
      app_record = {:hair => @hair, :frame => @frame}
      @record = @record.merge(app_record)
      @record.to_json
    end
end

al.extend Appearance
al.appearance_update

module Cadet
    def rank
      @rank = 'Cadet Sergeant'
    end
    def age
      @age += 1
    end
    def career_to_s
      rank
      age
      puts "#{@name} is a #{@age} year old #{@gender} #{@rank}."
    end
    def career_to_j
      @career_record = {:age => @age, :rank => @rank}
      @record = @record.merge(@career_record)
      @record.to_json
    end
end

al.extend Cadet
al.career_to_s # Al is a 15 year old Female Cadet Sergeant.
al_json = al.career_to_j

puts al_json #
{"name":"Al","age":15,"gender":"Female","hair":"Raider cut dishwater
blond","frame":"skinny","rank":"Cadet Sergeant"}

class Crew
    require 'json'
    def initialize(data)
      @data = JSON.parse(data, :symbolize_names => true)
      @name = @data[:name]
      @rank = @data[:rank]
    end
    def to_s
      puts "Welcome aboard, #{@rank} #{@name}!"
    end
end
my_crew = Crew.new(al_json)
my_crew.to_s # Welcome aboard, Cadet Sergeant Al!

#####

On 07/24/2018 06:08 AM, Andy Jones wrote:

Well, your UDS will have to put the player data in an object of some class. If not Player, then what?

Put another way, when an object "pulls data from the UDS" -- where does it hold it?

Generally speaking when reading from a data store you want one of more "models" -- objects that represent the data to the rest of the application. It's fine if you don't want Player to be the model, but whatever "models" player information to the rest of the application will need that rank attribute (and all the others from all those decorators) or you will have the same problem.

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam Hall
Sent: 24 July 2018 10:58
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another class?

Understood. Here's how I see that being dealt with.

1. There is a Universal Data Store (UDS) of some unrestricted format.
JSON, MOngoDB, or whatever. Each program pulls from the same data store.

2. The character generate program creates basic characters.

3. The game program:
     For a new game, generates crew. Thus uses the basic character
generation program and adds specific stuff required by the game.
     For a saved game, those characters are pulled from the UDS.

4. The fiction tracker program:
     For new characters:
       Does basic character generation
       Adds data like appearance, mental workup, last known location, etc.
     Otherwise pulls the data from the UDS.
     Allows manual edits of data.
     Allows creation of new data fields.

5. The relationship tracker feeds data into Neo4J.
     A basic character from #2 has no relational data.
     A game character from #3 would have the rest of the crew as
relationships.
     A fiction character might overload Neo4J, if you've seen how
convoluted my fiction gets...

In each case the programs include the modules they need. The UDS has
*all* the data, but each program only uses parts of it. The relationship
tracker doesn't care about physical appearance, etc.

By keeping things in modules, each program can use the modules it needs.
Does that solve the problems you are seeing?

I really appreciate the feedback, you are helping clarify my thinking.

Leam

On 07/23/2018 03:26 AM, Andy Jones wrote:

Again, I'm dubious that this is the best approach, because when you get an unknown method error half way into your main, game code, and it turns out that that's because you called person.rank on a Person that doesn't have the Career_Tools mixin, that is going to be painful to debug.

Alternatively you will have to pepper your code with things like `if defined?(person.rank); rank_thing(person.rank); end` which will make the code much harder to follow and ALL the bugs harder to find.

The way I would do it, personally? I know that some Persons have a rank. So I put rank right there in the Person class. Civilians have a rank of nil. Nil values are a PITA but considerably less so than missing methods! So now I can at least do `rank_thing(person.rank)` and I guess rank_thing starts with `return unless rank`...

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam
Hall
Sent: 21 July 2018 1:19 am
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another
class?

And this is how I spend my Friday night...

Thoughts? In this case Career_Tools has to account for anything in any
of the careers. Most of the time those things are pretty simple.

####

class Person
      def initialize(data)
        @name = data[:name]
      end
      def name
        @name
      end
end

module Career_Tools
      def rank
        @rank
      end
      def rank=(career)
        @rank = career.ranks
      end
end

module Relationships
      def friends
        @friends
      end
      def friends=(l)
        if @friends.nil?
          @friends = Array.new
        end
        @friends << l
      end
end

class Cadet
      def ranks
        ['Cadet', 'Cadet Corporal', 'Cadet Sergeant', 'Cadet
Leiutenant'].sample
      end
end

### main
person_data = {:name => 'Al'}
al = Person.new(person_data)
puts al.name
al.extend Career_Tools
al.extend Relationships
al.friends = "CC One"
al.friends = "Wilbur"
puts al.friends.join(", ")
career = Cadet.new
al.rank=(career)
puts al.rank

####

On 07/20/2018 03:48 PM, leam hall wrote:

This will be part of the weekend's joy.

There are lots of roles, and most instances of Person get at least one.
A few instances get more than one, but which role a particular instance
gets varies widely.

More questions as I stumble through...

Leam

On Thu, Jul 19, 2018 at 6:12 AM, Andy Jones <Andy.Jones@jameshall.co.uk >>>>> <mailto:Andy.Jones@jameshall.co.uk>> wrote:

       You have to ask yourself: composition or mixin? The easiest way is
       with a mixin, but it has limitations.

       ~~~~
       module Engineer
          def can_do_engines?; true; end
       end

       class Person
          def initialize(name); @name = name; end
       end

       p = Person.new("fred")
       p.extend Engineer
       puts p.can_do_engines?
       ~~~~

       Note that you decorate an object --- not a class. If you want to
       add to the functionality of every instance of a class, I'm not sure
       that counts as Decorator pattern?

       If you want to do it with Composition, instead, have a look at the
       Ruby documentation for Forwardable. (Sorry this is a bit rushed;
       chaotic today...)

       I think that this might be the article that originally clued me into
       Decorator? Not sure:
       https://robots.thoughtbot.com/evaluating-alternative-decorator-

implementations-in

       <https://robots.thoughtbot.com/evaluating-alternative-decorator-

implementations-in>

       -----Original Message-----
       From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org
       <mailto:ruby-talk-bounces@ruby-lang.org>] On Behalf Of Leam Hall
       Sent: 19 July 2018 10:59
       To: Ruby users
       Subject: Class that opens and adds variables to instances of another
       class?

       I think I'm trying to figure out the Decorator pattern. What I want is
       to have a <thing> that takes an instance of <something> and changes it.
       The changes can include modifying and adding instance variables.

       Haven't quite figured it out yet, thoughts?

       Thanks!

       Leam

       Here's the error:
       ###
       Traceback (most recent call last):
       3: from test_decorator.rb:28:in `<main>'
       2: from test_decorator.rb:28:in `new'
       1: from test_decorator.rb:17:in `initialize'
       test_decorator.rb:20:in `assign_role': undefined method `role=' for
       #<Person:0x0000562e9a870978 @data={:name=>"Al"}> (NoMethodError)
       ###

       Code:
       ###
           1 class Person
           2 def initialize(data)
           3 @data = data
           4 end
           5 def name
           6 @data[:name] || "Fred"
           7 end
           8 end
           9
          10 class Role
          11 class Person
          12 attr_accessor :role
          13 end
          14
          15 def initialize(data)
          16 @person = data[:person]
          17 assign_role(data[:role])
          18 end
          19 def assign_role(role)
          20 @person.role = role
          21 end
          22 end
          23
          24 data = {:name => 'Al'}
          25
          26 al = Person.new(data)
          27 role_data = {:person => al, :role => 'Cadet'}
          28 Role.new(role_data)
          29 puts al.name <http://al.name>
          30 #al.role = "Cadet Sergeant"
       ###

       Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org
       <mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>>

       Click here to view Company Information and Confidentiality
       Notice.<http://www.jameshall.co.uk/index.php/small-print/email-

disclaimer

       <http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>>

       Please note that we have updated our privacy policy in line with new
       data protection regulations. Please refer to our website to view the
       ways in which we handle your data.

       Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org
       <mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>


(Dmitriy Non) #23

Hi!

Sorry, I didn't read the whole thread. I was just curious what was the initial question.

I have some thoughts/advices/opinions on this:

* Role::Person naming is just bad. I had to actually read the implementation to understand
  what it is.I advice you not to use same constant names in Ruby. Even though it doesnt really "break" ruby
  you can shoot yourself in a leg with it.
* Well, at this point I know that I got a wrong idea about what you were trying to achieve.
  Problem in your code is that you think `class Role; class Person ...` actually changes original Person class.
  Well, it doesn't (I guess someone already pointed that for you). Classes are namespaces too, so `Person` and `Role::Person`
  are actually absolutely different constants that are not connected in any way.
* I like the idea of "mutability is bad". It really helps you to write better code.
  So don't mutate original instance with decorators. Try using more "classic" (or java-ish) approach:

  class Person
    # ...
  end

  class PersonWithRole
    attr_accessor :role
    attr_reader :decorated_person
    # it's from rails but you get the idea
    delegate :name, to: :decorated_person

    def initialize(person)
      @decorated_person = person
    end

    # ...
  end

  Decorator is like a wrapper over some object that provides additional functionality.
  My `PersonWithRole` does not even care about implementation of decorated person. It just adds `role` accessor
  that is stored in wrapper itself and proxies method calls to wrapped object (I did it with `delegate` but I guess
  that `method_missing` is better approach).
  If you want different namespace for decorators you can try something like `PersonDecorators::WithRole`

You should definetely read about Ruby constant lookup. I actually wrote a post on the toppic but it's in russian:(
I recommend you to read:

* Chapter 7.9 of "The Ruby Programming Language" (from Matz)
* https://guides.rubyonrails.org/autoloading_and_reloading_constants.html
* Play around with [Module.nesting](http://ruby-doc.org/core-2.2.0/Module.html#method-c-nesting)

I hope my late letter will be helpful. Sorry for my english

···

--------------------------------------
Dmitriy Non
non.dmitriy@gmail.com

On 19 Jul 2018, at 12:58, Leam Hall <leamhall@gmail.com> wrote:

I think I'm trying to figure out the Decorator pattern. What I want is to have a <thing> that takes an instance of <something> and changes it. The changes can include modifying and adding instance variables.

Haven't quite figured it out yet, thoughts?

Thanks!

Leam

Here's the error:
###
Traceback (most recent call last):
  3: from test_decorator.rb:28:in `<main>'
  2: from test_decorator.rb:28:in `new'
  1: from test_decorator.rb:17:in `initialize'
test_decorator.rb:20:in `assign_role': undefined method `role=' for #<Person:0x0000562e9a870978 @data={:name=>"Al"}> (NoMethodError)
###

Code:
###
1 class Person
2 def initialize(data)
3 @data = data
4 end
5 def name
6 @data[:name] || "Fred"
7 end
8 end
9
10 class Role
11 class Person
12 attr_accessor :role
13 end
14
15 def initialize(data)
16 @person = data[:person]
17 assign_role(data[:role])
18 end
19 def assign_role(role)
20 @person.role = role
21 end
22 end
23
24 data = {:name => 'Al'}
25
26 al = Person.new(data)
27 role_data = {:person => al, :role => 'Cadet'}
28 Role.new(role_data)
29 puts al.name
30 #al.role = "Cadet Sergeant"
###

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>


(Dmitriy Non) #24

I forgot to mention that "classic" decorators are nice because
they compose just fine as long as they don't have colliding public methods.

For example:

class Person
  def say_hi; 'hi!' end  
end

class PersonDecorator
  attr_reader :decorated_person
  def initialize(person)
    @decorated_person = person
  end

  # just send other calls to original person
  def method_missing(m, *args, &block)
    decorated_person.send(m, *args, &block)
  end
end

class PersonWithName < PersonDecorator
  attr_reader :name
  def initialize(person, name)
    super(person)
    @name = name
  end
end

class PersonWithAge < PersonDecorator
  attr_reader :age
  def initialize(person, age)
    super(person)
    @age = age
  end
end

person = PersonWithName.new(PersonWithAge.new(Person.new, 18), 'John Doe')
puts person.name
puts person.age
puts person.say_hi

Though there may be some problems when you are overriding original methods
but I am sure you will know how to solve this issue when you face it.

···

--------------------------------------
Dmitriy Non
non.dmitriy@gmail.com

On 29 Jul 2018, at 02:05, Dmitriy Non <non.dmitriy@gmail.com> wrote:

Hi!

Sorry, I didn't read the whole thread. I was just curious what was the initial question.

I have some thoughts/advices/opinions on this:

* Role::Person naming is just bad. I had to actually read the implementation to understand
what it is.I advice you not to use same constant names in Ruby. Even though it doesnt really "break" ruby
you can shoot yourself in a leg with it.
* Well, at this point I know that I got a wrong idea about what you were trying to achieve.
Problem in your code is that you think `class Role; class Person ...` actually changes original Person class.
Well, it doesn't (I guess someone already pointed that for you). Classes are namespaces too, so `Person` and `Role::Person`
are actually absolutely different constants that are not connected in any way.
* I like the idea of "mutability is bad". It really helps you to write better code.
So don't mutate original instance with decorators. Try using more "classic" (or java-ish) approach:

 class Person
   # ...
 end

 class PersonWithRole
   attr_accessor :role
   attr_reader :decorated_person
   # it's from rails but you get the idea
   delegate :name, to: :decorated_person

   def initialize(person)
     @decorated_person = person
   end

   # ...
 end

Decorator is like a wrapper over some object that provides additional functionality.
My `PersonWithRole` does not even care about implementation of decorated person. It just adds `role` accessor
that is stored in wrapper itself and proxies method calls to wrapped object (I did it with `delegate` but I guess
that `method_missing` is better approach).
If you want different namespace for decorators you can try something like `PersonDecorators::WithRole`

You should definetely read about Ruby constant lookup. I actually wrote a post on the toppic but it's in russian:(
I recommend you to read:

* Chapter 7.9 of "The Ruby Programming Language" (from Matz)
* https://guides.rubyonrails.org/autoloading_and_reloading_constants.html
* Play around with [Module.nesting](http://ruby-doc.org/core-2.2.0/Module.html#method-c-nesting)

I hope my late letter will be helpful. Sorry for my english

--------------------------------------
Dmitriy Non
non.dmitriy@gmail.com

On 19 Jul 2018, at 12:58, Leam Hall <leamhall@gmail.com> wrote:

I think I'm trying to figure out the Decorator pattern. What I want is to have a <thing> that takes an instance of <something> and changes it. The changes can include modifying and adding instance variables.

Haven't quite figured it out yet, thoughts?

Thanks!

Leam

Here's the error:
###
Traceback (most recent call last):
  3: from test_decorator.rb:28:in `<main>'
  2: from test_decorator.rb:28:in `new'
  1: from test_decorator.rb:17:in `initialize'
test_decorator.rb:20:in `assign_role': undefined method `role=' for #<Person:0x0000562e9a870978 @data={:name=>"Al"}> (NoMethodError)
###

Code:
###
1 class Person
2 def initialize(data)
3 @data = data
4 end
5 def name
6 @data[:name] || "Fred"
7 end
8 end
9
10 class Role
11 class Person
12 attr_accessor :role
13 end
14
15 def initialize(data)
16 @person = data[:person]
17 assign_role(data[:role])
18 end
19 def assign_role(role)
20 @person.role = role
21 end
22 end
23
24 data = {:name => 'Al'}
25
26 al = Person.new(data)
27 role_data = {:person => al, :role => 'Cadet'}
28 Role.new(role_data)
29 puts al.name
30 #al.role = "Cadet Sergeant"
###

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>


(Andy Jones) #25

Ah...I may be understanding. Slowly. Let me see if I have it.

1. There is a file called 'person.rb' that sets the basic Person class.
class Person
  def initialize(data)
    @name = data[:name]
  end
end

2. There is a program called "Starship" that uses 'crew.rb', a file that
opens the Person class.
class Person
  attr_reader :rank
  def setup_crew(data)
    @rank = data[:rank]
  end
end

3. Starship looks something like this:
# Create data from UDS pull.
data = {:name => 'Al', :rank => 'Captain'}

al = Person.new(data)
require 'crew'
al.setup_crew(data)
puts al.rank# "Captain" She was promoted. :slight_smile:

Is that what you're suggesting? I can pull the data from the UDS, put it
into a hash, and then send it to each "setup_" method.

Leam

···

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam Hall
Sent: 28 July 2018 15:10
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another class?

On 07/27/2018 06:54 AM, Andy Jones wrote:

1) All your classes output JSON, which is nice, but you don't show any classes that read that JSON, and I'm pretty certain that at that point you will find you want a single object, a Person model, to read in the JSON and represent it to the rest of the code. (And if you are going to do that later, you might as well do that now?)

2a) If all your person objects get decorated with Appearance and Cadet, then there is no point using Decorator Pattern. You might as well put it all in one class. Or use some other way to split up the code, which I agree might be a good idea if that is your intent. (Method Classes still seem the way to go, for me, in that eventuality.)

2b) If only some of the person objects get decorated, then you will never be able to call `person.rank` safely because you won't know if the object has a rank method or not. If this isn't a problem because you never plan to use these objects once they have built JSON for you, then you might as well just have a class to build JSON for you, and not represent person as an object at all. Which brings us back to (1).

TL;DR: My recommendation would be: Design the object that needs to read and represent all this JSON about a person first. Call _that_ the Person class, and work from there. It might clarify your thinking, and probably make for much shorter code.

Hope that helps...

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam Hall
Sent: 27 July 2018 11:19
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another class?

Hey Andy, this is my pre-workday proof of concept. Lots of ways to
improve it, I'm sure. Instead of a formal DB connection I'm using JSON
and passing it around. The last bit takes in the JSON and uses just what
it needs. In reality, the code would add a career to the Person if there
wasn't already one. I just wanted to see if I could pass the data around
well.

Where there is a "# blah...blah" after a puts or a to_s, that's the output.

Does this answer the questions you raised?

Thanks!

Leam

#####

class Person
    require 'json'
    def initialize()
      @name = 'Al'
      @age = 14
      @gender = 'Female'
    end
    def to_s
      puts "#{@name} is a #{@age} year old #{@gender}."
    end
    def to_j
      @record = {:name => @name, :age => @age, :gender => @gender}
      @record.to_json
    end
end

al = Person.new
al.to_s # Al is a 14 year old Female.
puts al.to_j # {"name":"Al","age":14,"gender":"Female"}

module Appearance
    def hair
      @hair = 'Raider cut dishwater blond'
    end
    def frame
      @frame = 'skinny'
    end
    def appearance_update
      hair
      frame
      app_record = {:hair => @hair, :frame => @frame}
      @record = @record.merge(app_record)
      @record.to_json
    end
end

al.extend Appearance
al.appearance_update

module Cadet
    def rank
      @rank = 'Cadet Sergeant'
    end
    def age
      @age += 1
    end
    def career_to_s
      rank
      age
      puts "#{@name} is a #{@age} year old #{@gender} #{@rank}."
    end
    def career_to_j
      @career_record = {:age => @age, :rank => @rank}
      @record = @record.merge(@career_record)
      @record.to_json
    end
end

al.extend Cadet
al.career_to_s # Al is a 15 year old Female Cadet Sergeant.
al_json = al.career_to_j

puts al_json #
{"name":"Al","age":15,"gender":"Female","hair":"Raider cut dishwater
blond","frame":"skinny","rank":"Cadet Sergeant"}

class Crew
    require 'json'
    def initialize(data)
      @data = JSON.parse(data, :symbolize_names => true)
      @name = @data[:name]
      @rank = @data[:rank]
    end
    def to_s
      puts "Welcome aboard, #{@rank} #{@name}!"
    end
end
my_crew = Crew.new(al_json)
my_crew.to_s # Welcome aboard, Cadet Sergeant Al!

#####

On 07/24/2018 06:08 AM, Andy Jones wrote:

Well, your UDS will have to put the player data in an object of some class. If not Player, then what?

Put another way, when an object "pulls data from the UDS" -- where does it hold it?

Generally speaking when reading from a data store you want one of more "models" -- objects that represent the data to the rest of the application. It's fine if you don't want Player to be the model, but whatever "models" player information to the rest of the application will need that rank attribute (and all the others from all those decorators) or you will have the same problem.

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam Hall
Sent: 24 July 2018 10:58
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another class?

Understood. Here's how I see that being dealt with.

1. There is a Universal Data Store (UDS) of some unrestricted format.
JSON, MOngoDB, or whatever. Each program pulls from the same data store.

2. The character generate program creates basic characters.

3. The game program:
     For a new game, generates crew. Thus uses the basic character
generation program and adds specific stuff required by the game.
     For a saved game, those characters are pulled from the UDS.

4. The fiction tracker program:
     For new characters:
       Does basic character generation
       Adds data like appearance, mental workup, last known location, etc.
     Otherwise pulls the data from the UDS.
     Allows manual edits of data.
     Allows creation of new data fields.

5. The relationship tracker feeds data into Neo4J.
     A basic character from #2 has no relational data.
     A game character from #3 would have the rest of the crew as
relationships.
     A fiction character might overload Neo4J, if you've seen how
convoluted my fiction gets...

In each case the programs include the modules they need. The UDS has
*all* the data, but each program only uses parts of it. The relationship
tracker doesn't care about physical appearance, etc.

By keeping things in modules, each program can use the modules it needs.
Does that solve the problems you are seeing?

I really appreciate the feedback, you are helping clarify my thinking.

Leam

On 07/23/2018 03:26 AM, Andy Jones wrote:

Again, I'm dubious that this is the best approach, because when you get an unknown method error half way into your main, game code, and it turns out that that's because you called person.rank on a Person that doesn't have the Career_Tools mixin, that is going to be painful to debug.

Alternatively you will have to pepper your code with things like `if defined?(person.rank); rank_thing(person.rank); end` which will make the code much harder to follow and ALL the bugs harder to find.

The way I would do it, personally? I know that some Persons have a rank. So I put rank right there in the Person class. Civilians have a rank of nil. Nil values are a PITA but considerably less so than missing methods! So now I can at least do `rank_thing(person.rank)` and I guess rank_thing starts with `return unless rank`...

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of Leam
Hall
Sent: 21 July 2018 1:19 am
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another
class?

And this is how I spend my Friday night...

Thoughts? In this case Career_Tools has to account for anything in any
of the careers. Most of the time those things are pretty simple.

####

class Person
      def initialize(data)
        @name = data[:name]
      end
      def name
        @name
      end
end

module Career_Tools
      def rank
        @rank
      end
      def rank=(career)
        @rank = career.ranks
      end
end

module Relationships
      def friends
        @friends
      end
      def friends=(l)
        if @friends.nil?
          @friends = Array.new
        end
        @friends << l
      end
end

class Cadet
      def ranks
        ['Cadet', 'Cadet Corporal', 'Cadet Sergeant', 'Cadet
Leiutenant'].sample
      end
end

### main
person_data = {:name => 'Al'}
al = Person.new(person_data)
puts al.name
al.extend Career_Tools
al.extend Relationships
al.friends = "CC One"
al.friends = "Wilbur"
puts al.friends.join(", ")
career = Cadet.new
al.rank=(career)
puts al.rank

####

On 07/20/2018 03:48 PM, leam hall wrote:

This will be part of the weekend's joy.

There are lots of roles, and most instances of Person get at least one.
A few instances get more than one, but which role a particular instance
gets varies widely.

More questions as I stumble through...

Leam

On Thu, Jul 19, 2018 at 6:12 AM, Andy Jones <Andy.Jones@jameshall.co.uk >>>>> <mailto:Andy.Jones@jameshall.co.uk>> wrote:

       You have to ask yourself: composition or mixin? The easiest way is
       with a mixin, but it has limitations.

       ~~~~
       module Engineer
          def can_do_engines?; true; end
       end

       class Person
          def initialize(name); @name = name; end
       end

       p = Person.new("fred")
       p.extend Engineer
       puts p.can_do_engines?
       ~~~~

       Note that you decorate an object --- not a class. If you want to
       add to the functionality of every instance of a class, I'm not sure
       that counts as Decorator pattern?

       If you want to do it with Composition, instead, have a look at the
       Ruby documentation for Forwardable. (Sorry this is a bit rushed;
       chaotic today...)

       I think that this might be the article that originally clued me into
       Decorator? Not sure:
       https://robots.thoughtbot.com/evaluating-alternative-decorator-

implementations-in

       <https://robots.thoughtbot.com/evaluating-alternative-decorator-

implementations-in>

       -----Original Message-----
       From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org
       <mailto:ruby-talk-bounces@ruby-lang.org>] On Behalf Of Leam Hall
       Sent: 19 July 2018 10:59
       To: Ruby users
       Subject: Class that opens and adds variables to instances of another
       class?

       I think I'm trying to figure out the Decorator pattern. What I want is
       to have a <thing> that takes an instance of <something> and changes it.
       The changes can include modifying and adding instance variables.

       Haven't quite figured it out yet, thoughts?

       Thanks!

       Leam

       Here's the error:
       ###
       Traceback (most recent call last):
       3: from test_decorator.rb:28:in `<main>'
       2: from test_decorator.rb:28:in `new'
       1: from test_decorator.rb:17:in `initialize'
       test_decorator.rb:20:in `assign_role': undefined method `role=' for
       #<Person:0x0000562e9a870978 @data={:name=>"Al"}> (NoMethodError)
       ###

       Code:
       ###
           1 class Person
           2 def initialize(data)
           3 @data = data
           4 end
           5 def name
           6 @data[:name] || "Fred"
           7 end
           8 end
           9
          10 class Role
          11 class Person
          12 attr_accessor :role
          13 end
          14
          15 def initialize(data)
          16 @person = data[:person]
          17 assign_role(data[:role])
          18 end
          19 def assign_role(role)
          20 @person.role = role
          21 end
          22 end
          23
          24 data = {:name => 'Al'}
          25
          26 al = Person.new(data)
          27 role_data = {:person => al, :role => 'Cadet'}
          28 Role.new(role_data)
          29 puts al.name <http://al.name>
          30 #al.role = "Cadet Sergeant"
       ###

       Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org
       <mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>>

       Click here to view Company Information and Confidentiality
       Notice.<http://www.jameshall.co.uk/index.php/small-print/email-

disclaimer

       <http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>>

       Please note that we have updated our privacy policy in line with new
       data protection regulations. Please refer to our website to view the
       ways in which we handle your data.

       Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org
       <mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk
       <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.


(Andy Jones) #26

TL;DR: Okay, look, if it were me I _really_ wouldn't do it this way, because it's going to be hell on wheels to debug. You're free to do it how you like, of course, but...

2. There is a program called "Starship" that uses 'crew.rb', a file that
opens the Person class.

Now every time you look at code that uses Person you will have to work out whether that code has also required crew.rb previously, because that will change the behaviour of Person. (In addition to working out which decorators were run on the specific instance of Person you are looking at...)

For my money it's much, much better to put the definition of Person in one file, person.rb, and know it won't change during the project. This is kind of fundamental to OOP, IMO: a class is a thing. It's not one of two things, depending.

3. Starship looks something like this:
# Create data from UDS pull.
data = {:name => 'Al', :rank => 'Captain'}

Where is this JSON data coming from? Does every class have the responsibility of parsing that JSON file? What if sections of that JSON file end up requiring non-trivial code to parse -- how many classes will that effect? If the format of the JSON file actually changes, how many classes will you have to change?

Again, in my opinion it's way better to have a single class that parses the JSON and creates a person from it. Then pass instances of that class to the other classes, not the JSON data.

Again, this is kind of fundamental to OOP: the "Single Responsibility Principal". Each class should do just one thing.

IMO the logical name for the class that represents person data to the rest of the application is ... Person. That's your person class.

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.


(Leam Hall) #27

Yes, changes to Person and the JSON data will propagate. Already did
that with the UPP going from a string of Hexidecimals to a Hash of
Int.

However, there's really just one Person class. The data is passed to
it to create the base person object and then supplementary modules are
mixed in to expand the methods and attributes Person is aware of.

···

On Mon, Jul 30, 2018 at 3:29 AM, Andy Jones <Andy.Jones@jameshall.co.uk> wrote:

TL;DR: Okay, look, if it were me I _really_ wouldn't do it this way, because it's going to be hell on wheels to debug. You're free to do it how you like, of course, but...

2. There is a program called "Starship" that uses 'crew.rb', a file that
opens the Person class.
<<<<<<<<

Now every time you look at code that uses Person you will have to work out whether that code has also required crew.rb previously, because that will change the behaviour of Person. (In addition to working out which decorators were run on the specific instance of Person you are looking at...)

For my money it's much, much better to put the definition of Person in one file, person.rb, and know it won't change during the project. This is kind of fundamental to OOP, IMO: a class is a thing. It's not one of two things, depending.

3. Starship looks something like this:
# Create data from UDS pull.
data = {:name => 'Al', :rank => 'Captain'}
<<<<<<<<

Where is this JSON data coming from? Does every class have the responsibility of parsing that JSON file? What if sections of that JSON file end up requiring non-trivial code to parse -- how many classes will that effect? If the format of the JSON file actually changes, how many classes will you have to change?

Again, in my opinion it's way better to have a single class that parses the JSON and creates a person from it. Then pass instances of that class to the other classes, not the JSON data.

Again, this is kind of fundamental to OOP: the "Single Responsibility Principal". Each class should do just one thing.

IMO the logical name for the class that represents person data to the rest of the application is ... Person. That's your person class.

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>


(Leam Hall) #28

Funny note: I had set POODR down for a week or two (or three). Picked it up last night before bed and read a section on hook methods which may exactly solve this issue. Code when I have it.


(Elizabeth Amisano) #29

Hello,
I am looking for a system to create a program that will help my daughter
learn to read. It will be a program that follows certain rules she is
learning with her tutor.

Two examples of a command I would ask the program to do would be to:

-highlight all vowels in the text that are backed up by a consonant
yellow....

-3 letters that are between highlight to red

I am brand new to coding but am determined to make a program that allows me
to input the text she will be reading and then based on the rule that is
learned in tutoring, apply them one at a time, then layer.

If there are any suggestions folks have for a system to use I would greatly
appreciate it.
Thank you
Elizabeth Amisano

···

On Wed, Aug 1, 2018 at 1:00 AM Leam Hall <leamhall@gmail.com> wrote:

Funny note: I had set POODR down for a week or two (or three). Picked it
up last night before bed and read a section on hook methods which may
exactly solve this issue. Code when I have it.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>


(Nicola Mingotti) #30

Hi Elizabeth,

so, I wrote you a very simple program that goes in the direction
you say. You can see a screenshot here :

The code is at the bottom.

1] I put a lot of comments to simplify your reading

2] There is some stuff in italian, forgive me is 2:00 am, in the next days i will
have other stuff to do, so now or never;) Anyhow, GoogleTranslate where needed :wink:

3] I wrote it fast, about 2 hours, it took me most of the time to find
how to make the graphical interface do what i want to.

4] The graphical interface i like for scripting is Tk, probably the simplest.

5] since i wrote it fast, it should be easier to understand than well written code.
But, consider this is just a script, not a serius program, it is just to get your started.

6] I am really interested in knowing if you can run the program. Please
let me know that. I need to know if a non-programmer can run a Ruby program
with a simple interface, since we discussed about this recently in the list.

7] There are tons of typos, sorry for that.

enough cha cha cha, here is the script

···

========================
#!/usr/local/bin/ruby
# ; -*- mode: Ruby;-*-

require "irb"
require "irb/completion"

require "tk"
require "tk/font"

root = TkRoot.new { title "Text Hilighter" }

# size and place of the window
root.minsize [500, 100]
root.geometry "+300+300"

# first push button
b1 = TkButton.new root do
text "Hilight Stuff" ;
command proc { pushedHilight; }
pack :side => "top", :fill => "x", :expand => "true"
end

# second push button
b2 = TkButton.new root do
text "Clean Highligthing" ;
command proc { pushedCleanHighlighting; }
pack :side => "top", :fill => "x", :expand => "true"
end

# text widget, where you write stuff
# here it is a global variable because we need to access it from functions
$t1 = TkText.new root do
pack :side => "top", :fill => "both", :expand => "true"
end

# ---- some commands for Tk Text widget -----
# $t1.insert :end "helo" # inserisce una parola alla fine
# $t1.delte 1.0, :end # cacella tutto
# $t1.get 1.0, :end # ottiene tutto il testo
# $t1.tag_add :xx, 1.0, 1.2 # taggo con :xx i primi due caratteri nella prima riga
# $t1.tag_configure :xx, :background, "yellow" # il tag :xx viene impostato a background yellow
# $t1.tag_delete :xx # elimina i tag dal testo
# $t1.index :end # ritorna con x.y che e' l'ultimo carattere

def pushedHilight
# pattern, that says: I want all strings starting with any of the letter a,e,i,o,u
# and followed by any of the letter {b,c,d, ... }
# the "i" at the ends means we ignore case of letters
pattern = /[aeiou][bcdfghjklmnpqrstvwyz]/i
puts "pushed button: Hilight"
# get all the text from the Tk Text widget
text = $t1.get 1.0, :end
# split the text in lines
lines = text.split /\n/;
# iterate over each lines looking for the pattern we are interested into
(0...lines.length).each do |idx|
# lines in a Tk Text widget are numbered starting from 1 (unfortunately)
# but charactes in each line are numbered starting from 0 (we like this more)
tclLineIdx = idx + 1
# "li" is the line i am currently observing
li = lines[idx]
puts "line: #{idx} -- #{li}"
# "pe" (pattern end) at the beginnig is set to zero, we have not found still any
# pattern.
pe = 0
# now we look for all pattern occurring in "li"
loop do
pb = li.index(pattern, pe)
# if no pattern is found "pb" (pattern begin) is "nil", we can exit the study
# of the current line.
break if pb == nil
pe = Regexp.last_match.end(0)
puts "line #{idx}, pb: #{pb}, pe: #{pe} "
# this line is the one telling to Tk to mark each patter with the tag :xx (just a random name)
$t1.tag_add :xx, "#{tclLineIdx}.#{pb}", "#{tclLineIdx}.#{pe-1}"
end
end
# here we say that what is tagged as tag ":xx" must be hilighted in yellow
$t1.tag_configure :xx, :background, "yellow"
end

def pushedCleanHighlighting
puts "pushed button: Clean-Hilight"
# delete all tags, so remove all hilights
$t1.tag_delete :xx
end

# this thing is usefull for debugging and learn Ruby
# decomment next line when studying.
# Thread.new { IRB.start(__FILE__) }

# start the event loop (waits for button pushed and other events)
Tk.mainloop;

========================

On 08/21/18 21:07, Elizabeth Amisano wrote:

Hello,
I am looking for a system to create a program that will help my daughter learn to read. It will be a program that follows certain rules she is learning with her tutor.

Two examples of a command I would ask the program to do would be to:

-highlight all vowels in the text that are backed up by a consonant yellow....

-3 letters that are between highlight to red

I am brand new to coding but am determined to make a program that allows me to input the text she will be reading and then based on the rule that is learned in tutoring, apply them one at a time, then layer.

If there are any suggestions folks have for a system to use I would greatly appreciate it.
Thank you
Elizabeth Amisano

On Wed, Aug 1, 2018 at 1:00 AM Leam Hall <leamhall@gmail.com > <mailto:leamhall@gmail.com>> wrote:

    Funny note: I had set POODR down for a week or two (or three).
    Picked it
    up last night before bed and read a section on hook methods which may
    exactly solve this issue. Code when I have it.

    Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org
    <mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
    <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

--
--------------------------
Dr. Nicola Mingotti
R&D - Borghi Srl
CTO - BondInsider
--------------------------


(Andy Jones) #31

The way Ruby thinks of OOP borrows a lot from Smalltalk.

I’m not a Smalltalk programmer, but the syntax is ridiculously easy to learn, and this book pays me back every time I read it: https://www.amazon.com/Smalltalk-Best-Practice-Patterns-Kent/dp/013476904X

(Although for gods’ sake don’t pay £50 for it …)

···

From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org] On Behalf Of leam hall
Sent: 19 July 2018 14:29
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another class?

Hah! I may need a Smalltalk primer, I keep seeing references to it.

Yes, I think your last example is closer to the need and a Decorator might not be what I'm looking for. The simple explination is:

al = Person.new
Something_That_Changes_Character.new(al)
Something_Else_That_Also_Changes_Character.new(al)

In each case "al" has several attributes modified or added. The "al" passed to the second Something carries the modifications from the first.

Leam

On Thu, Jul 19, 2018 at 9:22 AM, Andy Jones <Andy.Jones@jameshall.co.uk<mailto:Andy.Jones@jameshall.co.uk>> wrote:
Well, first, it seems as if you might find it easier to keep the classes for character creation and the actual game somewhat different. Have the game read the JSON into a separate “model” class that keeps it away from the complexities of character creation, and just lets it deal with the resulting character? I don’t know.

Second, it seems to me that all your characters want to partake in all your addon functionality. The use case for Decorator Pattern is rather different. It’s popular in game design, but not usually used in that way, but rather like this:

flora = Character.new(“Flora the elven druid”)
flora.exend Elf
flora.exend Druid

varian = Character.new(“Varian the dwarven undead druid”)
varian.extend Dwarf
varian.extend Undead
varian.extend Druid

(Apologies if the formatting is odd, Outlook is playing Silly Devils)

If I understand correctly, you might want to go to what Smalltalk used to call “Method Classes” – where you farm off some of your functionality to a helper class that does a job, returns a result, and then never gets called again. It seems like your CharacterCreation might best be that sort of class. But I don’t know it well enough to say for sure.

class Person
  def initialize(name, strength)
    @name, @strength = name, strength
    @foo = ValueOfFooFigurerOuter.new(strength).go
  end
end

In the above example, the Person class doesn’t have to care how @foo is calculated. A helper class does that for it.

Hope that helps…

From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org<mailto:ruby-talk-bounces@ruby-lang.org>] On Behalf Of leam hall
Sent: 19 July 2018 13:57
To: Ruby users
Subject: Re: Class that opens and adds variables to instances of another class?

Hmm..good question. This stems from my Traveller stuff and the game I'm writing, which have two different uses for the Person class. Here's the logic:

1. Every instance of Person
    Has a name, gender, and UPP. (character stats)
    Can be stored in a language agnostic manner (using JSON)

2. CharacterCreation adds to an instance of Person
    Adds physical description, personality

3. Careers add to the instance of person
    Skills, rank, notes, money, "stuff"
    A character can go through multiple careers.

4. FreeTrader (the game) uses a limited subset of the Person + Additional stuff.
    Ship position(role),
    Game uses JSON
    Uses Careers, might use CharacterCreation.

5. CharacterData (the database) stores as much information about each character as possible.
    Planning to use Hanami/Sinatra? and a MongoDB backend.
    Used to track details of characters for games and books I'm writing.

FreeTrader and CharacterData are separate projects but I want to build from the same Person base and extend from there. That's why the instance of a Person might have some attributes and not others.

Leam

On Thu, Jul 19, 2018 at 6:12 AM, Andy Jones <Andy.Jones@jameshall.co.uk<mailto:Andy.Jones@jameshall.co.uk>> wrote:
You have to ask yourself: composition or mixin? The easiest way is with a mixin, but it has limitations.

~~~~
module Engineer
  def can_do_engines?; true; end
end

class Person
  def initialize(name); @name = name; end
end

p = Person.new("fred")
p.extend Engineer
puts p.can_do_engines?
~~~~

Note that you decorate an object --- not a class. If you want to add to the functionality of every instance of a class, I'm not sure that counts as Decorator pattern?

If you want to do it with Composition, instead, have a look at the Ruby documentation for Forwardable. (Sorry this is a bit rushed; chaotic today...)

I think that this might be the article that originally clued me into Decorator? Not sure: https://robots.thoughtbot.com/evaluating-alternative-decorator-implementations-in

-----Original Message-----
From: ruby-talk [mailto:ruby-talk-bounces@ruby-lang.org<mailto:ruby-talk-bounces@ruby-lang.org>] On Behalf Of Leam Hall
Sent: 19 July 2018 10:59
To: Ruby users
Subject: Class that opens and adds variables to instances of another class?

I think I'm trying to figure out the Decorator pattern. What I want is
to have a <thing> that takes an instance of <something> and changes it.
The changes can include modifying and adding instance variables.

Haven't quite figured it out yet, thoughts?

Thanks!

Leam

Here's the error:
###
Traceback (most recent call last):
3: from test_decorator.rb:28:in `<main>'
2: from test_decorator.rb:28:in `new'
1: from test_decorator.rb:17:in `initialize'
test_decorator.rb:20:in `assign_role': undefined method `role=' for
#<Person:0x0000562e9a870978 @data={:name=>"Al"}> (NoMethodError)
###

Code:
###
   1 class Person
   2 def initialize(data)
   3 @data = data
   4 end
   5 def name
   6 @data[:name] || "Fred"
   7 end
   8 end
   9
  10 class Role
  11 class Person
  12 attr_accessor :role
  13 end
  14
  15 def initialize(data)
  16 @person = data[:person]
  17 assign_role(data[:role])
  18 end
  19 def assign_role(role)
  20 @person.role = role
  21 end
  22 end
  23
  24 data = {:name => 'Al'}
  25
  26 al = Person.new(data)
  27 role_data = {:person => al, :role => 'Cadet'}
  28 Role.new(role_data)
  29 puts al.name<http://al.name>
  30 #al.role = "Cadet Sergeant"
###
Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org<mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>
Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org<mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org<mailto:ruby-talk-request@ruby-lang.org>?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.