Thank you Jeremy!

As usual - a very quick and helpful reply.  (I don't know how you manage to 
monitor/answer all the questions is Sequel & Roda and still write/maintain 
code... ;)

Agree with you reasoning that the examples are wrong (I knew I'd have to do 
some checking first to decide if I should do the assignment).  I was trying 
to limit the size of an already long-winded question (sorry about the 
length...).

I'm 100% in agreement with not implementing features people don't need/use 
- too many features is often the problem with software far more often than 
too few features.

Thank you for the feedback and pointers.  For now I will implement some 
setters in my models and if I run into it again I'll extract them to ensure 
they work for multiple occurances.  Totally understand being low on your 
list.  If I need it for more then one occurrence I'll ensure I have specs, 
tests, and updated docs and do a pull request.

Thank you!
-John

On Sunday, December 20, 2015 at 10:06:24 PM UTC-8, Jeremy Evans wrote:
>
> On Sunday, December 20, 2015 at 9:03:34 PM UTC-8, John Manuel wrote:
>>
>> I think I may be misunderstanding how to properly use *one_through_one*, 
>> so I'd like to ask some advice about it.
>>
>> *Here is summary of my situation/database:*
>> * A database originally created and used by another application has 2 
>> unrelated tables which I want to relate in a new application I'm building. 
>>  Here is a simplified migration for the two tables:
>>     create_table :accounts do
>>       primary_key   :id
>>       citext        :email, null: false, index: true, unique: true
>>       ...
>>       DateTime      :updated_at, null: false
>>       DateTime      :created_at, null: false
>>     end
>>
>>
>>     create_table :people do
>>       primary_key     :id
>>       String          :last_name, null: false, default: ''
>>       String          :first_name, null: false, default: ''
>>       String          :middle_name, size: 40
>>       jsonb           :address_fields, default: nil
>>       jsonb           :phone_fields, default: nil
>>       TrueClass       :publish, null: false, default: true
>>       ...
>>       DateTime        :updated_at, null: false
>>       DateTime        :created_at, null: false
>>     end
>> * You can see from the tables that they are not related (no foreign keys 
>> or references to each other).
>> * I figure the best option is to add a new table other applications will 
>> ignore that I can use in this new application that needs to relate these - 
>> like this:
>>     create_table :accounts_people do
>>       foreign_key     :account_id, :accounts, index: true, unique: true, 
>> null: false
>>       foreign_key     :person_id, :people, index: true, unique: true, 
>> null: false
>>       primary_key     [:account_id, :person_id]
>>       index           [:account_id, :person_id], unique: true
>>     end
>>
>
> Note that as account_id and person_id are part of the primary key, you 
> don't need to use the null: false option for them.  You don't need a unique 
> index that is the same as the primary key, as the primary key will create 
> an implicit index.
>  
>
>> * With that I should have a clean way to relate accounts and people - 
>> it's just a regular join table, but not many-to-many. Each account can be 
>> associated with 0 or 1 people, and each person can only be related to 0 or 
>> 1 accounts.  I would use create_join_table(), but that only makes the 
>> primary_key unique, not each foreign key (which is required to ensure only 
>> 0 or 1 associations).
>> * I thought the Sequel models for this would be very simple (again, 
>> simplified):
>> class Account < Sequel::Model
>>   one_through_one   :person
>>   ...
>> end
>>
>> class Person < Sequel::Model
>>   one_through_one   :account
>>   ...
>> end
>>
>> *Problem:*
>> Now, for me the problem comes because (as the documentation states: 
>> *one_through_one 
>> associations do not have any modification methods added.*)
>> With the above models I can query if an account has a person or a person 
>> has an account; but there are no methods to associate a person with an 
>> account (or account with a person).  Before carefully reading the 
>> documentation I would have expected one_through_one to setup the same 
>> setters as the one_to_one association (but set the keys in the join table 
>> instead of the right or left table).  However one_through_one only sets up 
>> getters.
>>
>
> The main reason Sequel doesn't setup setter methods is that nobody has 
> requested setter methods yet.  I'm not opposed to adding them to 
> one_through_one associations.
>  
>
>>
>> *Solutions?:*
>> I've thought of three potential ways to provide setters on my models so 
>> that I can associate people and accounts, but I want to know if I'm missing 
>> something and off in the weeds....
>>
>> Option 1: (seems easy but wrong)
>> * Add many_to_many relationships to both models in addition to the 
>> one_through_one.
>> * I tried this, and it works, but it doesn't seem 'honest' when reading 
>> the code and implies the relationships are many_to_many.
>> class Account < Sequel::Model
>>   many_to_many      :people
>>   one_through_one   :person
>>   ...
>> end
>>
>> class Person < Sequel::Model
>>   many_to_many      :accounts
>>   one_through_one   :account
>>   ...
>> end
>> * But now in code I end up with account.add_person(person) and/or 
>> person.add_account(account) both of which imply the standard 
>> many-to-many relationship.
>> * So, I'd end up using the many_to_many setters to set (the one 
>> account/person) and the one_through_one getters to access the related 
>> account/person.
>> * I'd think if this was the right solution Jeremy would have documented 
>> that.
>>
>
> This is wrong because:
>
>   account.add_person(person1)
>   account.add_person(person2)
>
> would result in an error instead of setting the person for the account to 
> person2.
>  
>
>> Both of the next options require that I create a model for the join table 
>> (AccountsPeople) which I'd rather not do because I don't really 'care' 
>> about that model - I just want the relationships.
>> Option 2:
>> * Create the AccountsPeople model with associations to Account and Person.
>> class AccountsPerson < Sequel::Model
>>   many_to_one      :account
>>   many_to_one      :person
>> end
>>
>> Now I need to get or create an account and a person and then associate 
>> them using a new AccountsPerson instance like this:
>> AccountsPerson.create(account: account, person: person)
>> -or-
>> ap = AccountsPerson.new
>> ap.person = get_the_person()
>> ap.account = get_the_account()
>> ap.save
>> This requires my client code to know and understand there is an 
>> AccountsPerson model when I don't think it should (it's just a 
>> relationship).
>>
>
> Wrong for the same reason.
>  
>
>> Option 3:
>> * Seems the most correct way, but also the most code/work
>> * Extend option 2 by adding my own setters on Account and Person, like so:
>> class Account < Sequel::Model
>>   one_through_one   :person
>>   
>>   def person=(person)
>>     AccountsPerson.create(account: self, person: person)
>>   end
>> end
>>
>> class Person < Sequel::Model
>>   one_through_one   :account
>>   
>>   def account=(account)
>>     AccountsPerson.create(account: account, person: self)
>>   end
>> end
>>
>> From a client (application) code point of view the 3rd options seems the 
>> cleanest.  The fact that an AccountsPerson model exists is limited to 
>> Person and Account and any application code doesn't need to know about it.
>>
>
> Also wrong for the same reason. 
>
> I'm just wondering if I've missed something (likely) in Sequel that makes 
>> the use of one_through_one more simple and obvious. I'm certainly not 
>> opposed to implementing option 3 - I implemented all 3 options so I could 
>> clearly understand my problem and write this question.  It just seems that 
>> after implementing option 3 why would I need to use one_through_one at all 
>> - I may as well just write the getters also and make the code 
>> more symmetrical.
>>
>> If one_though_one doesn't provide any setter capability for the 
>> relationship, then what's the 'right' way to do this and is one_through_one 
>> intended to be a read-only association?  If so, it would be helpful to have 
>> that clearly stated in the documentation with an example of how to create 
>> the relationship.  More likely, I've missed something and just need to 
>> understand it.
>>
>
> Currently, it is intended to be a read-only association, since it doesn't 
> provide modification methods.  This is documented (
> http://sequel.jeremyevans.net/rdoc/files/doc/association_basics_rdoc.html#label-Methods+Added
> ).
>
> The reason the one_through_one association was added to Sequel was because 
> it was a simple modification to the many_to_many support.  The setter 
> methods for one_through_one would be new code, and few users need them, so 
> I decided not to implement them when adding the initial support. 
>  Considering it's been almost 2 years since one_through_one and you are the 
> first person to request setter methods for one_through_one, that's not 
> looking like a bad decision in hindsight.
>
> I don't think supporting the setter methods is that difficult.  The 
> Account#person= setter method is basically calling the equivalent code:
>
>   if current_person != person
>     remove_person(current_person) if current_person
>     add_person(person) if person
>   end
>
> I'll certainly consider a patch that implements them, with the usual 
> provisos (include specs, include integration tests, update documentation). 
>  I'll add it to my todo list, but I can't promise it any time soon, and 
> certainly I won't have a chance to implement it before the next release.
>
> Thanks,
> Jeremy
>

-- 
You received this message because you are subscribed to the Google Groups 
"sequel-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sequel-talk.
For more options, visit https://groups.google.com/d/optout.

Reply via email to