#3088 - [PATCH] Make has_one with :conditions hash scope build or creation of object with those conditions

Hi guys,

I created a patch on Lighthouse [1] that makes has_one associations
with conditions provided with a hash be built or created with those
conditions. As it is explained in the doc added:

"Record creation from the association is scoped if a hash is used.
has_one :account, :conditions => {:enabled => true} will create an
enabled account with @company.create_account or
@company.build_account"

I needed to support this in an app I'm working on, and I think it's a
small patch that might be useful for others too. The ticket has been
there with a couple of days so I thought that maybe through this
message someone will be interested and comment on it :slight_smile:

Thanks!

[1]
https://rails.lighthouseapp.com/projects/8994/tickets/3088-make-has_one-with-conditions-hash-scope-build-or-creation-of-object-with-those-conditions

I like the concept and think it should be expanded to other
associations. We already define certain attributes of associated
objects for selecting, the symmetry of having build/create defaults is
handy.

I could use it on a current project right now:
has_many :trial_memberships, :conditions => {:state => 'trial',
:starts_on => Time.now}

@company.trial_memberships.build(other_opts_that_merge)
or
@company.trial_memberships.create

would set initial state and starts_on values for objects associated
through these associations.

Comments:
1) :conditions is a bad name choice (even if the concept is not
expanded to other association types). Other associations already use
:conditions to define database selects
(example from the docs: has_many :approved_comments, :class_name =>
'Comment', :conditions => ['approved = ?', true])

Use :build_options, :build_attributes, :initial_attributes, etc.

2) HasMany and HABTM Associations already have a mechanism that could
be used for setting initial attributes (the Association callbacks).
has_many : trial_memberships, :before_add =>
:set_initial_state_to_trial, :set_starts_at_to_now

has_one/belongs_to don't have these callbacks.

I'd love to see all associations get callbacks. these could be used
internally for setting initial attributes on associated objects,
making
has_many : trial_memberships, :initial_attriubtes => {:state =>
'trial', :starts_at => Time.now}
a shorthand syntax for
has_many : trial_memberships, :after_add =>
:set_initial_state_to_trial, :set_starts_at_to_now

-Trek

Isn't this stuff that is taken care of by :default on a column when
creating migrations?

I like the concept and think it should be expanded to other
associations. We already define certain attributes of associated
objects for selecting, the symmetry of having build/create defaults is
handy.

I could use it on a current project right now:
has_many :trial_memberships, :conditions => {:state => 'trial',
:starts_on => Time.now}

@company.trial_memberships.build(other_opts_that_merge)
or
@company.trial_memberships.create

would set initial state and starts_on values for objects associated
through these associations.

Actually, I think that behaviour is already there for has_many and
habtm (and what this patch does is extend the same behavior to
has_one):

:conditions - Specify the conditions that the associated objects must
meet in order to be included as a WHERE SQL fragment, such as price >
5 AND name LIKE ‘B%’. Record creations from the association are scoped
if a hash is used. has_many :posts, :conditions => {:published =>
true} will create published posts with @blog.posts.create or
@blog.posts.build

Comments:
1) :conditions is a bad name choice (even if the concept is not
expanded to other association types). Other associations already use
:conditions to define database selects
(example from the docs: has_many :approved_comments, :class_name =>
'Comment', :conditions => ['approved = ?', true])

Use :build_options, :build_attributes, :initial_attributes, etc.

2) HasMany and HABTM Associations already have a mechanism that could
be used for setting initial attributes (the Association callbacks).
has_many : trial_memberships, :before_add =>
:set_initial_state_to_trial, :set_starts_at_to_now

has_one/belongs_to don't have these callbacks.

I'd love to see all associations get callbacks. these could be used
internally for setting initial attributes on associated objects,
making
has_many : trial_memberships, :initial_attriubtes => {:state =>
'trial', :starts_at => Time.now}
a shorthand syntax for
has_many : trial_memberships, :after_add =>
:set_initial_state_to_trial, :set_starts_at_to_now

I feel that callbacks for has_one/belongs_to associations might be too
much, but maybe it could be a good progression from this patch.

Thanks for looking at it and sharing your comments :slight_smile:

Isn't this stuff that is taken care of by :default on a column when
creating migrations?

If you do it at the database level, then all your records will have
those defaults. On the contrary it's just that has_one association
that you might want to have some defaults based on the conditions you
have. For example with "has_one :account, :conditions => {:enabled =>
true}", you only want the account from that model to be enabled by
default, but for a regular account you would still want it to be
false.

Oooops, you're totally right. I guess I've always used the array
syntax for :conditions. In that case, :conditions is definitely the
best name for the option and the patch brings has_one into alignment
with other association types.

Clearly I haven't been using this feature on the collection
association types, but now that I know about it, I'll be refactoring
away some sloppier code. I'd like to have it available for has_one +1.