[LRUG] Chat Digest, Vol 185, Issue 5

ismael celis ismaelct at gmail.com
Wed Jun 23 05:04:08 PDT 2021


I don’t agree with the Anemic Domain Object assessment. It depends on what the domain _is_. Separating data from behaviour by having simple structs for data and policy or strategy objects for permissions/grants seems reasonable. It just happens that your domain calls for elevating grant policies into their own domain objects. If anything that’s _better_ object orientation because you can rely on polymorphism to have different policy implementations with the same simple interface.

IMO you could take this as far as separating structs from storage via the repository pattern (the ROM-rb project comes to mind), or just rely on ActiveRecord but keeping those objects data-oriented (ie. very few public instance methods, and only for direct data access) and moving behaviour into policies/strategies.

To sum up I agree that `BookPolicy.new(user, book).access?` or even `BookPolicy.access?(user, book)` is a pretty reasonable way of doing it.

Ismael

> On 22 Jun 2021, at 17:05, chat-request at lists.lrug.org wrote:
> 
> Send Chat mailing list submissions to
> 	chat at lists.lrug.org
> 
> To subscribe or unsubscribe via the World Wide Web, visit
> 	http://lists.lrug.org/listinfo.cgi/chat-lrug.org
> or, via email, send a message with subject or body 'help' to
> 	chat-request at lists.lrug.org
> 
> You can reach the person managing the list at
> 	chat-owner at lists.lrug.org
> 
> When replying, please edit your Subject line so it is more specific
> than "Re: Contents of Chat digest..."
> 
> 
> Today's Topics:
> 
>   1. Objects that are neither classes nor records but something in
>      between (Tim Cowlishaw)
>   2. Re: Objects that are neither classes nor records but
>      something in between (Ed Jones)
>   3. Re: Objects that are neither classes nor records but
>      something in between (Alberto Fern?ndez-Capel)
>   4. Re: Objects that are neither classes nor records but
>      something in between (Tim Cowlishaw)
>   5. Re: Objects that are neither classes nor records but
>      something in between (Andrew Donovan)
> 
> 
> ----------------------------------------------------------------------
> 
> Message: 1
> Date: Tue, 22 Jun 2021 16:43:28 +0200
> From: Tim Cowlishaw <tim at timcowlishaw.co.uk>
> To: Ruby Group <chat at lists.lrug.org>
> Subject: [LRUG] Objects that are neither classes nor records but
> 	something in between
> Message-ID:
> 	<CAMugUeJLRE=_XYZNFAn19KVp3cwgbCjfeNDwgcYDMR0E88_Rog at mail.gmail.com>
> Content-Type: text/plain; charset="utf-8"
> 
> Hi there folks! Hope you're all doing well.
> 
> 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.
> 
> 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.
> 
> An example:
> 
> I'm currently working on  a project which is a digital library. It contains
> lots of Books:
> 
> class Book < Struct.new(:id, :title, :author, :content); end
> 
> 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:
> 
> class User
>  def initialize(purchases=[])
>    @purchases = purchases
>   end
>   attr_reader :purchases
> 
>   def has_access_to?(book)
>     purchases.any? { |p| p.grants_access_to?(book) }
>   end
> end
> 
> class Purchase < Struct.new(:product)
>  def grants_access_to?(book)
>    product.grants_access_to?(book)
>  end
> end
> 
> class Product
>  def self.price
>   raise NotImplementedError
>  end
> 
>  def self.name
>    raise NotImplementedError
>  end
> 
>  def grants_access_to?(book)
>    raise NotImplementedError
>  end
> end
> 
> class OneOffPurchase < Purchase
>  def initialize(book)
>    @book = book
>  end
>  attr_reader :book
> 
>  def self.price
>    500
>  end
> 
>  def self.name
>    "A single book"
>  end
> 
>  def grants_access_to?(other_book)
>    book.id == other_book.id
>  end
> end
> 
> def Subscription < Purchase
>  def self.price
>    500
>  end
> 
>  def self.name
>    "All inclusive special subscription"
>  end
> 
>  def grants_access_to?(book)
>    true
>  end
> end
> 
> 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:
> 
> class Product < Struct.new(:unique_key, :name, :price, :book)
>  def grants_accesss_to?(other_book)
>     if unique_key = :one_off && book.present?
>       book.id == other_book.id
>   elsif unique_key == :one_off
>      raise "oops! we're in a totally inconsistent state
>    elsif unique_key == :subscription && book.present?
>       raise "yep, this makes absolutely no sense either"
>    elsif unique_key == :subscription
>      true
>   else
>     raise "this isn't even a real product type. hopeless."
>    end
> end
> 
> 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?
> 
> Therefore, anyone got any smart ideas about how to do this better? The
> requirements I'm looking to fulfil, in summary:
> 
> 1) A  product has a specific 'strategy' for granting access to a book for a
> user, expressed as  ruby code
> 2) A product has a price and name that is editable as data through our web
> app's admin interface
> 
> 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.
> 
> Any thoughts gratefully received!
> 
> Cheers,
> 
> Tim
> -------------- next part --------------
> An HTML attachment was scrubbed...
> URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20210622/d6eccbc5/attachment-0001.html>
> 
> ------------------------------
> 
> Message: 2
> Date: Tue, 22 Jun 2021 16:04:04 +0100
> From: Ed Jones <ed at error.agency>
> To: London Ruby Users Group <chat at lists.lrug.org>
> Subject: Re: [LRUG] Objects that are neither classes nor records but
> 	something in between
> Message-ID: <6B5F3AB2-CD83-41FC-9193-EC32D40FD6AC at error.agency>
> Content-Type: text/plain; charset="us-ascii"
> 
> Hi Tim
> 
> Are you using Rails for this stuff? Regardless of whether it's a Rails project or not, I like using Pundit for problems like this - it abstracts away the authorisation from the model quite nicely. https://github.com/varvet/pundit <https://github.com/varvet/pundit>
> 
> So in your case you might have a BookPolicy with an access?(user, book) method, which might look like this:
> 
> class BookPolicy
> 	attr_reader :user, :book
> 
> 	def initialize(user, book)
> 		@user = user
> 		@book = book
> 	end
> 
> 	def access?
> 		user.has_subscription? || user.purchases.includes?(book)
> 	end
> end
> 
> Then wherever you need to check if a user can access a book, you just do BookPolicy.new(user, book).access? and your logic to determine that is encapsulated away nicely in the policy class. (Or if you're doing it in Rails controllers, there are helper methods).
> 
> This example is predicated on having some sort of way to determine if the user has a subscription or not; (assuming Rails) you could have a polymorphic association to different types of purchase to address this.
> 
> Not sure if this is helpful or not!
> 
> Ed
> 
>> On 22 Jun 2021, at 15:43, Tim Cowlishaw <tim at timcowlishaw.co.uk> wrote:
>> 
>> Hi there folks! Hope you're all doing well.
>> 
>> 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.
>> 
>> 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.
>> 
>> An example:
>> 
>> I'm currently working on  a project which is a digital library. It contains lots of Books:
>> 
>> class Book < Struct.new(:id, :title, :author, :content); end
>> 
>> 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:
>> 
>> class User
>>  def initialize(purchases=[])
>>    @purchases = purchases
>>   end
>>   attr_reader :purchases
>> 
>>   def has_access_to?(book)
>>     purchases.any? { |p| p.grants_access_to?(book) }
>>   end
>> end
>> 
>> class Purchase < Struct.new(:product)
>>  def grants_access_to?(book)
>>    product.grants_access_to?(book)
>>  end 
>> end
>> 
>> class Product
>>  def self.price
>>   raise NotImplementedError
>>  end
>> 
>>  def self.name <http://self.name/>
>>    raise NotImplementedError
>>  end
>> 
>>  def grants_access_to?(book)
>>    raise NotImplementedError
>>  end
>> end
>> 
>> class OneOffPurchase < Purchase
>>  def initialize(book)
>>    @book = book
>>  end
>>  attr_reader :book
>> 
>>  def self.price
>>    500
>>  end
>> 
>>  def self.name <http://self.name/>
>>    "A single book"
>>  end
>> 
>>  def grants_access_to?(other_book)
>>    book.id <http://book.id/> == other_book.id <http://other_book.id/>
>>  end
>> end
>> 
>> def Subscription < Purchase
>>  def self.price
>>    500
>>  end
>> 
>>  def self.name <http://self.name/>
>>    "All inclusive special subscription"
>>  end
>> 
>>  def grants_access_to?(book)
>>    true
>>  end
>> end
>> 
>> 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:
>> 
>> class Product < Struct.new(:unique_key, :name, :price, :book)
>>  def grants_accesss_to?(other_book)
>>     if unique_key = :one_off && book.present?
>>       book.id <http://book.id/> == other_book.id <http://other_book.id/>
>>   elsif unique_key == :one_off
>>      raise "oops! we're in a totally inconsistent state
>>    elsif unique_key == :subscription && book.present?
>>       raise "yep, this makes absolutely no sense either"
>>    elsif unique_key == :subscription
>>      true
>>   else
>>     raise "this isn't even a real product type. hopeless."
>>    end
>> end
>> 
>> 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?
>> 
>> Therefore, anyone got any smart ideas about how to do this better? The requirements I'm looking to fulfil, in summary:
>> 
>> 1) A  product has a specific 'strategy' for granting access to a book for a user, expressed as  ruby code
>> 2) A product has a price and name that is editable as data through our web app's admin interface
>> 
>> 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.
>> 
>> Any thoughts gratefully received!
>> 
>> Cheers,
>> 
>> Tim
>> 
>> _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
>> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
>> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
> 
> -------------- next part --------------
> An HTML attachment was scrubbed...
> URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20210622/f6c1ce4e/attachment-0001.html>
> 
> ------------------------------
> 
> Message: 3
> Date: Tue, 22 Jun 2021 16:27:54 +0100
> From: Alberto Fern?ndez-Capel <afcapel at gmail.com>
> To: London Ruby Users Group <chat at lists.lrug.org>
> Subject: Re: [LRUG] Objects that are neither classes nor records but
> 	something in between
> Message-ID:
> 	<CAPQUYXpxZXAVDPwcT4AvwU+wwQcT0iLOBj-vRt=xYsZRv4zmug at mail.gmail.com>
> Content-Type: text/plain; charset="utf-8"
> 
>> 
>> 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?
> 
> 
> What you describe sounds a lot like an anemic domain model
> <https://martinfowler.com/bliki/AnemicDomainModel.html>.
> 
>> From Martin Fowler's article:
> 
> The fundamental horror of this anti-pattern is that it's so contrary to the
>> basic idea of object-oriented design; which is to combine data and process
>> together. The anemic domain model is really just a procedural style design,
>> exactly the kind of thing that object bigots like me (and Eric) have been
>> fighting since our early days in Smalltalk. What's worse, many people think
>> that anemic objects are real objects, and thus completely miss the point of
>> what object-oriented design is all about.
> 
> 
> -- Alberto
> 
> On Tue, Jun 22, 2021 at 3:48 PM Tim Cowlishaw <tim at timcowlishaw.co.uk>
> wrote:
> 
>> Hi there folks! Hope you're all doing well.
>> 
>> 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.
>> 
>> 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.
>> 
>> An example:
>> 
>> I'm currently working on  a project which is a digital library. It
>> contains lots of Books:
>> 
>> class Book < Struct.new(:id, :title, :author, :content); end
>> 
>> 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:
>> 
>> class User
>>  def initialize(purchases=[])
>>    @purchases = purchases
>>   end
>>   attr_reader :purchases
>> 
>>   def has_access_to?(book)
>>     purchases.any? { |p| p.grants_access_to?(book) }
>>   end
>> end
>> 
>> class Purchase < Struct.new(:product)
>>  def grants_access_to?(book)
>>    product.grants_access_to?(book)
>>  end
>> end
>> 
>> class Product
>>  def self.price
>>   raise NotImplementedError
>>  end
>> 
>>  def self.name
>>    raise NotImplementedError
>>  end
>> 
>>  def grants_access_to?(book)
>>    raise NotImplementedError
>>  end
>> end
>> 
>> class OneOffPurchase < Purchase
>>  def initialize(book)
>>    @book = book
>>  end
>>  attr_reader :book
>> 
>>  def self.price
>>    500
>>  end
>> 
>>  def self.name
>>    "A single book"
>>  end
>> 
>>  def grants_access_to?(other_book)
>>    book.id == other_book.id
>>  end
>> end
>> 
>> def Subscription < Purchase
>>  def self.price
>>    500
>>  end
>> 
>>  def self.name
>>    "All inclusive special subscription"
>>  end
>> 
>>  def grants_access_to?(book)
>>    true
>>  end
>> end
>> 
>> 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:
>> 
>> class Product < Struct.new(:unique_key, :name, :price, :book)
>>  def grants_accesss_to?(other_book)
>>     if unique_key = :one_off && book.present?
>>       book.id == other_book.id
>>   elsif unique_key == :one_off
>>      raise "oops! we're in a totally inconsistent state
>>    elsif unique_key == :subscription && book.present?
>>       raise "yep, this makes absolutely no sense either"
>>    elsif unique_key == :subscription
>>      true
>>   else
>>     raise "this isn't even a real product type. hopeless."
>>    end
>> end
>> 
>> 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?
>> 
>> Therefore, anyone got any smart ideas about how to do this better? The
>> requirements I'm looking to fulfil, in summary:
>> 
>> 1) A  product has a specific 'strategy' for granting access to a book for
>> a user, expressed as  ruby code
>> 2) A product has a price and name that is editable as data through our web
>> app's admin interface
>> 
>> 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.
>> 
>> Any thoughts gratefully received!
>> 
>> Cheers,
>> 
>> Tim
>> 
>> _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
>> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
>> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>> 
> -------------- next part --------------
> An HTML attachment was scrubbed...
> URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20210622/520786b2/attachment-0001.html>
> 
> ------------------------------
> 
> Message: 4
> Date: Tue, 22 Jun 2021 17:52:40 +0200
> From: Tim Cowlishaw <tim at timcowlishaw.co.uk>
> To: London Ruby Users Group <chat at lists.lrug.org>
> Subject: Re: [LRUG] Objects that are neither classes nor records but
> 	something in between
> Message-ID:
> 	<CAMugUeJrYZtzSdiLAm75DYtAkq3Rt3Xe7T9zWy-HBk=TM2O8fw at mail.gmail.com>
> Content-Type: text/plain; charset="utf-8"
> 
> Aha thanks Ed! This is super useful - I'm not actually using Rails this
> time, but my way of approaching the problem was definitely informed by
> Warden / Cancancan and their ilk -  I just don't think i'd followed that
> thought through to it's logical conclusions!
> 
> You kinda hint at this too, but this kinda pushes the problem down into
> that User#has_subscription? method - the user's purchased Products may or
> may not contain a Product which happens to have 'subscription' behaviour,
> but the solution kinda falls straight out of flipping the problem around
> this way - I just need a #is_subscription? flag on products which governs
> how they behave wrt authorization. nice and simple!
> 
> Thanks very much :-D
> 
> Cheers!
> 
> Tim
> 
> On Tue, 22 Jun 2021 at 17:10, Ed Jones <ed at error.agency> wrote:
> 
>> Hi Tim
>> 
>> Are you using Rails for this stuff? Regardless of whether it's a Rails
>> project or not, I like using Pundit for problems like this - it abstracts
>> away the authorisation from the model quite nicely.
>> https://github.com/varvet/pundit
>> 
>> So in your case you might have a BookPolicy with an access?(user, book)
>> method, which might look like this:
>> 
>> class BookPolicy
>> attr_reader :user, :book
>> 
>> def initialize(user, book)
>> @user = user
>> @book = book
>> end
>> 
>> def access?
>> user.has_subscription? || user.purchases.includes?(book)
>> end
>> end
>> 
>> Then wherever you need to check if a user can access a book, you just do BookPolicy.new(user,
>> book).access? and your logic to determine that is encapsulated away
>> nicely in the policy class. (Or if you're doing it in Rails controllers,
>> there are helper methods).
>> 
>> This example is predicated on having some sort of way to determine if the
>> user has a subscription or not; (assuming Rails) you could have a
>> polymorphic association to different types of purchase to address this.
>> 
>> Not sure if this is helpful or not!
>> 
>> Ed
>> 
>> On 22 Jun 2021, at 15:43, Tim Cowlishaw <tim at timcowlishaw.co.uk> wrote:
>> 
>> Hi there folks! Hope you're all doing well.
>> 
>> 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.
>> 
>> 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.
>> 
>> An example:
>> 
>> I'm currently working on  a project which is a digital library. It
>> contains lots of Books:
>> 
>> class Book < Struct.new(:id, :title, :author, :content); end
>> 
>> 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:
>> 
>> class User
>>  def initialize(purchases=[])
>>    @purchases = purchases
>>   end
>>   attr_reader :purchases
>> 
>>   def has_access_to?(book)
>>     purchases.any? { |p| p.grants_access_to?(book) }
>>   end
>> end
>> 
>> class Purchase < Struct.new(:product)
>>  def grants_access_to?(book)
>>    product.grants_access_to?(book)
>>  end
>> end
>> 
>> class Product
>>  def self.price
>>   raise NotImplementedError
>>  end
>> 
>>  def self.name
>>    raise NotImplementedError
>>  end
>> 
>>  def grants_access_to?(book)
>>    raise NotImplementedError
>>  end
>> end
>> 
>> class OneOffPurchase < Purchase
>>  def initialize(book)
>>    @book = book
>>  end
>>  attr_reader :book
>> 
>>  def self.price
>>    500
>>  end
>> 
>>  def self.name
>>    "A single book"
>>  end
>> 
>>  def grants_access_to?(other_book)
>>    book.id == other_book.id
>>  end
>> end
>> 
>> def Subscription < Purchase
>>  def self.price
>>    500
>>  end
>> 
>>  def self.name
>>    "All inclusive special subscription"
>>  end
>> 
>>  def grants_access_to?(book)
>>    true
>>  end
>> end
>> 
>> 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:
>> 
>> class Product < Struct.new(:unique_key, :name, :price, :book)
>>  def grants_accesss_to?(other_book)
>>     if unique_key = :one_off && book.present?
>>       book.id == other_book.id
>>   elsif unique_key == :one_off
>>      raise "oops! we're in a totally inconsistent state
>>    elsif unique_key == :subscription && book.present?
>>       raise "yep, this makes absolutely no sense either"
>>    elsif unique_key == :subscription
>>      true
>>   else
>>     raise "this isn't even a real product type. hopeless."
>>    end
>> end
>> 
>> 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?
>> 
>> Therefore, anyone got any smart ideas about how to do this better? The
>> requirements I'm looking to fulfil, in summary:
>> 
>> 1) A  product has a specific 'strategy' for granting access to a book for
>> a user, expressed as  ruby code
>> 2) A product has a price and name that is editable as data through our web
>> app's admin interface
>> 
>> 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.
>> 
>> Any thoughts gratefully received!
>> 
>> Cheers,
>> 
>> Tim
>> 
>> _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
>> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
>> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>> 
>> 
>> _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
>> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
>> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>> 
> -------------- next part --------------
> An HTML attachment was scrubbed...
> URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20210622/09f9a525/attachment.html>
> 
> ------------------------------
> 
> Message: 5
> Date: Tue, 22 Jun 2021 16:00:33 +0000
> From: Andrew Donovan <andrew_donovan at hotmail.com>
> To: London Ruby Users Group <chat at lists.lrug.org>
> Subject: Re: [LRUG] Objects that are neither classes nor records but
> 	something in between
> Message-ID:
> 	<AM7PR09MB373385E0DECD5EC5EC68DFD8E8099 at AM7PR09MB3733.eurprd09.prod.outlook.com>
> 	
> Content-Type: text/plain; charset="utf-8"
> 
> Hi Tim,
> 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 ?
> Regards
> Andrew
> 
> 
> 
> Regards
> Andrew
> 
> From: Chat <chat-bounces at lists.lrug.org> On Behalf Of Tim Cowlishaw
> Sent: 22 June 2021 15:43
> To: Ruby Group <chat at lists.lrug.org>
> Subject: [LRUG] Objects that are neither classes nor records b.ut something in between
> 
> Hi there folks! Hope you're all doing well.
> 
> 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.
> 
> 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.
> 
> An example:
> 
> I'm currently working on  a project which is a digital library. It contains lots of Books:
> 
> class Book < Struct.new(:id, :title, :author, :content); end
> 
> 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:
> 
> class User
>  def initialize(purchases=[])
>    @purchases = purchases
>   end
>   attr_reader :purchases
> 
>   def has_access_to?(book)
>     purchases.any? { |p| p.grants_access_to?(book) }
>   end
> end
> 
> class Purchase < Struct.new(:product)
>  def grants_access_to?(book)
>    product.grants_access_to?(book)
>  end
> end
> 
> class Product
>  def self.price
>   raise NotImplementedError
>  end
> 
>  def self.name<http://self.name>
>    raise NotImplementedError
>  end
> 
>  def grants_access_to?(book)
>    raise NotImplementedError
>  end
> end
> 
> class OneOffPurchase < Purchase
>  def initialize(book)
>    @book = book
>  end
>  attr_reader :book
> 
>  def self.price
>    500
>  end
> 
>  def self.name<http://self.name>
>    "A single book"
>  end
> 
>  def grants_access_to?(other_book)
>    book.id<http://book.id> == other_book.id<http://other_book.id>
>  end
> end
> 
> def Subscription < Purchase
>  def self.price
>    500
>  end
> 
>  def self.name<http://self.name>
>    "All inclusive special subscription"
>  end
> 
>  def grants_access_to?(book)
>    true
>  end
> end
> 
> 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:
> 
> class Product < Struct.new(:unique_key, :name, :price, :book)
>  def grants_accesss_to?(other_book)
>     if unique_key = :one_off && book.present?
>       book.id<http://book.id> == other_book.id<http://other_book.id>
>   elsif unique_key == :one_off
>      raise "oops! we're in a totally inconsistent state
>    elsif unique_key == :subscription && book.present?
>       raise "yep, this makes absolutely no sense either"
>    elsif unique_key == :subscription
>      true
>   else
>     raise "this isn't even a real product type. hopeless."
>    end
> end
> 
> 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?
> 
> Therefore, anyone got any smart ideas about how to do this better? The requirements I'm looking to fulfil, in summary:
> 
> 1) A  product has a specific 'strategy' for granting access to a book for a user, expressed as  ruby code
> 2) A product has a price and name that is editable as data through our web app's admin interface
> 
> 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.
> 
> Any thoughts gratefully received!
> 
> Cheers,
> 
> Tim
> 
> -------------- next part --------------
> An HTML attachment was scrubbed...
> URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20210622/0ba31d03/attachment.html>
> 
> ------------------------------
> 
> Subject: Digest Footer
> 
> _______________________________________________
> Chat mailing list
> Chat at lists.lrug.org
> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
> 
> 
> ------------------------------
> 
> End of Chat Digest, Vol 185, Issue 5
> ************************************



More information about the Chat mailing list