<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="Generator" content="Microsoft Word 15 (filtered medium)">
<style><!--
/* Font Definitions */
@font-face
        {font-family:"Cambria Math";
        panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
        {font-family:Calibri;
        panose-1:2 15 5 2 2 2 4 3 2 4;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin:0cm;
        font-size:11.0pt;
        font-family:"Calibri",sans-serif;}
a:link, span.MsoHyperlink
        {mso-style-priority:99;
        color:blue;
        text-decoration:underline;}
span.EmailStyle18
        {mso-style-type:personal-reply;
        font-family:"Calibri",sans-serif;
        color:windowtext;}
.MsoChpDefault
        {mso-style-type:export-only;
        font-size:10.0pt;
        font-family:"Calibri",sans-serif;
        mso-fareast-language:EN-US;}
@page WordSection1
        {size:612.0pt 792.0pt;
        margin:72.0pt 72.0pt 72.0pt 72.0pt;}
div.WordSection1
        {page:WordSection1;}
/* List Definitions */
@list l0
        {mso-list-id:1819221820;
        mso-list-type:hybrid;
        mso-list-template-ids:-76115780 134807569 134807577 134807579 134807567 134807577 134807579 134807567 134807577 134807579;}
@list l0:level1
        {mso-level-text:"%1\)";
        mso-level-tab-stop:none;
        mso-level-number-position:left;
        text-indent:-18.0pt;}
@list l0:level2
        {mso-level-number-format:alpha-lower;
        mso-level-tab-stop:none;
        mso-level-number-position:left;
        text-indent:-18.0pt;}
@list l0:level3
        {mso-level-number-format:roman-lower;
        mso-level-tab-stop:none;
        mso-level-number-position:right;
        text-indent:-9.0pt;}
@list l0:level4
        {mso-level-tab-stop:none;
        mso-level-number-position:left;
        text-indent:-18.0pt;}
@list l0:level5
        {mso-level-number-format:alpha-lower;
        mso-level-tab-stop:none;
        mso-level-number-position:left;
        text-indent:-18.0pt;}
@list l0:level6
        {mso-level-number-format:roman-lower;
        mso-level-tab-stop:none;
        mso-level-number-position:right;
        text-indent:-9.0pt;}
@list l0:level7
        {mso-level-tab-stop:none;
        mso-level-number-position:left;
        text-indent:-18.0pt;}
@list l0:level8
        {mso-level-number-format:alpha-lower;
        mso-level-tab-stop:none;
        mso-level-number-position:left;
        text-indent:-18.0pt;}
@list l0:level9
        {mso-level-number-format:roman-lower;
        mso-level-tab-stop:none;
        mso-level-number-position:right;
        text-indent:-9.0pt;}
ol
        {margin-bottom:0cm;}
ul
        {margin-bottom:0cm;}
--></style><!--[if gte mso 9]><xml>
<o:shapedefaults v:ext="edit" spidmax="1026" />
</xml><![endif]--><!--[if gte mso 9]><xml>
<o:shapelayout v:ext="edit">
<o:idmap v:ext="edit" data="1" />
</o:shapelayout></xml><![endif]-->
</head>
<body lang="EN-GB" link="blue" vlink="purple" style="word-wrap:break-word">
<div class="WordSection1">
<p class="MsoNormal"><span style="mso-fareast-language:EN-US">Hi Tim,<o:p></o:p></span></p>
<p class="MsoNormal"><span style="mso-fareast-language:EN-US">Your db or permanent storage is your source of truth , if you’re using Rails then use all the facilities it offers such as ActiveRecord. If Product is somehow defining prices and other behaviour
 for your digital items then it needs to be modelled in the db. If you don’t have a db schema I’d draw out a simply entity relationship diagram which should help in identifying where things are missing or don’t tie up. From the gist of the details below it
 sounds like you have access to a database. I’d be wary of using code for defining product strategy, amongst other things how do you record product changes over time ?
<o:p></o:p></span></p>
<p class="MsoNormal"><span style="mso-fareast-language:EN-US">Regards<o:p></o:p></span></p>
<p class="MsoNormal"><span style="mso-fareast-language:EN-US">Andrew</span><o:p></o:p></p>
<p class="MsoNormal"><o:p> </o:p></p>
<p class="MsoNormal"><o:p> </o:p></p>
<p class="MsoNormal"><o:p> </o:p></p>
<p class="MsoNormal"><span style="mso-fareast-language:EN-US">Regards<o:p></o:p></span></p>
<p class="MsoNormal"><span style="mso-fareast-language:EN-US">Andrew<o:p></o:p></span></p>
<p class="MsoNormal"><span style="mso-fareast-language:EN-US"><o:p> </o:p></span></p>
<div style="border:none;border-top:solid #E1E1E1 1.0pt;padding:3.0pt 0cm 0cm 0cm">
<p class="MsoNormal"><b><span lang="EN-US">From:</span></b><span lang="EN-US"> Chat <chat-bounces@lists.lrug.org>
<b>On Behalf Of </b>Tim Cowlishaw<br>
<b>Sent:</b> 22 June 2021 15:43<br>
<b>To:</b> Ruby Group <chat@lists.lrug.org><br>
<b>Subject:</b> [LRUG] Objects that are neither classes nor records b.ut something in between<o:p></o:p></span></p>
</div>
<p class="MsoNormal"><o:p> </o:p></p>
<div>
<div>
<p class="MsoNormal">Hi there folks! Hope you're all doing well.<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">I've just run into a problem / conundrum / anti-pattern / code smell that seems to recur fairly frequently in projects I've worked on of late, and which i'm not really sure how to name in order to search for an existing solution, so i figured
 I'd post it here in case anyone has some sensible advice, or in the hope that it provokes some interesting discussion, at least.<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">The problem basically arises when there's some 'object' in my system (using this word in the vaguest possible sense for now) which has both *data* (so fields that can be updated through an API or webapp, and which should probably be stored
 in a database row), but also has a one-to-one correspondence with a specific bit of behaviour, expressed as code.<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">An example:<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">I'm currently working on  a project which is a digital library. It contains lots of Books:<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">class Book < Struct.new(:id, :title, :author, :content); end</span><o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">Users can buy access to these books either individually (all books are the same price), or by buying a subscription to the whole library. Forgetting the DB / ORM side of this for a sec, i'd model a toy implementation of what i want to do
 something like the following:<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">class User</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def initialize(purchases=[])</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    @purchases = purchases</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">   end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">   attr_reader :purchases</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">   def has_access_to?(book)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">     purchases.any? { |p| p.grants_access_to?(book) }</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">   end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">class Purchase < Struct.new(:product)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def grants_access_to?(book)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    product.grants_access_to?(book)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end </span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">class Product</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def self.price</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">   raise NotImplementedError</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def </span><a href="http://self.name"><span style="font-family:"Courier New"">self.name</span></a><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    raise NotImplementedError</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def grants_access_to?(book)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    raise NotImplementedError</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">class OneOffPurchase < Purchase</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def initialize(book)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    @book = book</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  attr_reader :book</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def self.price</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    500</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def </span><a href="http://self.name"><span style="font-family:"Courier New"">self.name</span></a><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    "A single book"</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def grants_access_to?(other_book)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    </span><a href="http://book.id"><span style="font-family:"Courier New"">book.id</span></a><span style="font-family:"Courier New""> ==
</span><a href="http://other_book.id"><span style="font-family:"Courier New"">other_book.id</span></a><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">def Subscription < Purchase</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def self.price</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    500</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def </span><a href="http://self.name"><span style="font-family:"Courier New"">self.name</span></a><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    "All inclusive special subscription"</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def grants_access_to?(book)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    true</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal">end<o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">This all to me looks reasonably sensible so far, but i also want those product names and prices to be editable by site admins through our 'backoffice' webapp, so they need to be stored in the DB, and this is where I have trouble finding
 a solution i'm happy with. Naively I could have an ActiveRecord type object and a fairly simple dispatch mechanism to the code that works out the permissions, but this has some fairly obvious flaws:<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">class Product < Struct.new(:unique_key, :name, :price, :book)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">  def grants_accesss_to?(other_book)</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">     if unique_key = :one_off && book.present?</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">       </span><a href="http://book.id"><span style="font-family:"Courier New"">book.id</span></a><span style="font-family:"Courier New""> ==
</span><a href="http://other_book.id"><span style="font-family:"Courier New"">other_book.id</span></a><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">   elsif unique_key == :one_off</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">      raise "oops! we're in a totally inconsistent state</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    elsif unique_key == :subscription && book.present?</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">       raise "yep, this makes absolutely no sense either"</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    elsif unique_key == :subscription</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">      true</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">   else</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">     raise "this isn't even a real product type. hopeless."</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">    end</span><o:p></o:p></p>
</div>
<div style="margin-left:30.0pt">
<p class="MsoNormal"><span style="font-family:"Courier New"">end</span><o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">aside from all the brittleness and smells that you can see above, this is also kinda useless in a bunch of respects - we can't add new product types without making a change to the codebase, we have to do a bunch of convoluted validation
 to avoid all the various possible inconsistent states we can get into above, and our codebase is tightly coupled to the value of that unique_key database field, which i'm very suspicious of?<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">Therefore, anyone got any smart ideas about how to do this better? The requirements I'm looking to fulfil, in summary:<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">1) A  product has a specific 'strategy' for granting access to a book for a user, expressed as  ruby code<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal">2) A product has a price and name that is editable as data through our web app's admin interface<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">And, in general, i'd be interested to know -  is there a name for this type of (anti-)pattern - Types of things that refuse to sensibly sit in the domain of software classes or Database fields? Does any of this even make any sense? I'm
 not so sure myself anymore.<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">Any thoughts gratefully received!<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">Cheers,<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">Tim<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
</div>
</div>
</body>
</html>