apotonick / disposable Goto Github PK
View Code? Open in Web Editor NEWDecorators on top of your ORM layer.
License: MIT License
Decorators on top of your ORM layer.
License: MIT License
If populator only adds/removes elements from collection, changed? returns nil.
To detect collection change - at least one member must be changed.
But it seems counter intuitive - if I added something to collection, that means, that collection as whole is changed, isn't it?
I'm using a collection property to handle scopes in ActiveRecord. Example:
class Cart
has_many :items
end
class Item
belongs_to :product, polymorphic: true
belongs_to :cart
scope :shoes, -> { where(product_type: "Shoe") }
end
class Shoe
has_many :items
end
And my twins:
class CartTwin
property :items, twin: ItemCollectionTwin
end
class ItemCollectionTwin
collection :shoes, twin: ShoeItemTwin
end
class ShoeItemTwin
property :product, twin: ShoeTwin
property :cart, twin: CartTwin
end
class ShoeTwin
property :items, twin: ItemCollectionTwin
end
So this all works from a read standpoint. It fails as soon as I sync from either end node. The problem is that *CollectionTwin
want to do shoes=
, but it's a scope and not a column. If i do collection :shoes, twin: ShoeItemTwin, writeable: false
it "syncs" but stops saving recursively at this point.
I need to be able to say "This property/collection has no write, but it still contains something that needs to be synced".
That's probably not intended.
Would sync'ing the model to the twin be an anti-pattern? Sometimes it happens that callbacks in the model (yeah I know) would change an attribute, which is a property in the twin which in turn would now be stale.
Hi Nick,
I wonder whether it is possible or not to somehow order collection. Something like:
class AlbumTwin < Disposable::Twin
collection :songs, order: [:by_duration, :by_created] do
property :name
property :duration
end
end
class Album < ActiveRecord::Base
scope :by_duration, -> { order(by_duration: :asc) }
scope :by_created, -> { order(by_created: :desc) }
end
Well, I mean, I know that it is not in current API right now, but maybe I could somehow implement desired behavior?
Hello, this method is useful when constructing a default record for button at has_many
form, because it does not append the record to base form collection. When constructing a nested twin using any other method I am losing parent
property value from Disposable::Twin::Parent
. Can you make it public, please?
Hope I have explained the thing I am trying to deal with good enough. I can bring more explanation if not. Thanks.
I have what I believe is a pretty direct translation of the collection item removal from the gemgem code but for some reason the record from the collection is never actually destroyed.
I've put all the related code in this gist and added comments to the item deletion test showing the values I'm seeing in the reform/disposable instance at each stage: https://gist.github.com/kevinansfield/5ac5c612921418ea874a#file-crud_test-rb-L72
Do you have any idea what's causing the deletion to fail? Thanks!
Hello,
We did a full bundle update today while upgrading our Rails project and ran into the following issue:
We are using Reform v2.0.3 and with the full update, Disposable was bumped to v0.1.10. In an #update, the form calls #validate on a param which changes all but one of the form fields to Uber::Options::Values. I have included the controller, form and test outputs below.
Report Controller:
def update
authorize! :manage, 'Report'
report = Report.find(params[:id])
form = RemoteReportForm.new(report)
if form.validate(params[:remote_report])
form.save do |hash|
report.update_attributes('report' => hash.stringify_keys)
end
redirect_to report_path(params[:id])
else
render :edit
end
end
Form:
class RemoteReportForm < Reform::Form
property :id
property :name
property :notes
property :a_ref
validates :name, presence: true
module RemoteReportFormExtensions
def name=(name)
self.w_ref = Website.where(name: name).try(:first).try(:id)
super(name)
end
end
collection :anchors, populate_if_empty: Reports::Anchor do
property :id
property :name
property :w_ref
property :_destroy
include RemoteReportFormExtensions
collection :feeders, populate_if_empty: Reports::Feeder do
property :id
property :name
property :w_ref
property :_destroy
include RemoteReportFormExtensions
end
end
end
Test:
describe 'PUT update' do
it 'should update the report' do
expected = {
'report' => {
'name' => 'New name',
'id' => 23,
'notes' => 'These are some notes',
'a_ref' => 56,
'anchors' => []
}
}
put :update, id: 23, remote_report: { name: 'New name' }
expect(report).to have_received(:update_attributes).with(expected)
end
end
Output:
form before #validate is called:
=> #<RemoteReportForm:0x007fe01f5cd698
@_changes={},
@fields={"id"=>23, "name"=>"One", "notes"=>"These are some notes", "a_ref"=>56, "anchors"=>[]},
@mapper=#<#<Class:0x007fe01f5ccb80>:0x007fe01c2edc28 @model=#<InstanceDouble(Report) (anonymous)>>, @model=#<InstanceDouble(Report) (anonymous)>>
form after #validate is called:
form.validate(params[:remote_report])
form
=> #<RemoteReportForm:0x007fe004b74288
@_changes={"id"=>true, "name"=>true, "notes"=>true, "account_ref"=>true},
@errors=#<Reform::Contract::Errors:0x007fe00488b238 @base=#<RemoteReportForm:0x007fe004b74288 ...>, @messages={}>,
@fields=
{"id"=>#<Uber::Options::Value:0x007fe01c149930 @callable=false, @dynamic=false, @method=false, @proc=false, @value=nil>,
"name"=>"New name",
"notes"=>#<Uber::Options::Value:0x007fe01f3a5078 @callable=false, @dynamic=false, @method=false, @proc=false, @value=nil>,
"account_ref"=>#<Uber::Options::Value:0x007fe01f33c460 @callable=false, @dynamic=false, @method=false, @proc=false, @value=nil>,
"anchors"=>[]},
@mapper=#<#<Class:0x007fe01f5ccb80>:0x007fe004b741c0 @model=#<InstanceDouble(Report) (anonymous)>>,
@model=#<InstanceDouble(Report) (anonymous)>>
As you can see, before #validate is called, the form retains it's original form values and after validation, they are changed to Uber::Options::Values. The only form field that does not change is the 'name' which is also the only attribute with a validation in the RemoteReportForm.
Model = Struct.new(:content)
class Nested < Disposable::Twin
property :nested_property
end
class Outer < Disposable::Twin
include Property::Hash
property :content, field: hash, twin: Nested
end
Outer.new(Model.new({nested_property: 1})) # will fail with message:
# NoMethodError: undefined method `nested_property' for {:nested_property=>1}}:Hash
The reason of failing is that the Property::Hash
module includes the following three modules (NestedDefaults
, Property::Struct
, Hash::Sync
) in the nested class by means of the feature
mechanism which works only for nested fields defined in the block but not in the separate class.
Since this feature
behaviour seems to be correct (in general modules should be explicitly included in the classes) I can see two possible ways to solve an issue:
class Nested < Disposable::Twin
feature Disposable::Twin::Property::Hash::NestedDefaults
feature Disposable::Twin::Property::Struct
feature Disposable::Twin::Property::Hash::Sync
property :nested_property
end
It's nicer to have just one module that should be included and which will do this work. Something like this:
class Nested < Disposable::Twin
feature Disposable::Twin::Property::Hashable
property :nested_property
end
Sequel by design does not save entire object graphs. Its association modification methods are designed to be very direct and not offer a lot of abstraction.
Rather than creating the records inline, we need to support creating the associated objects and then adding them to the parent.
Another option would be to make use of the nested attributes plugin but that might be even messier...
On master, as well as at least in versions 0.3.2+, one can neither clone nor duplicate a Twin as expected. Property changes in one of the twins will propagate to all twins.
class TestTwin < Disposable::Twin
property :foo
end
original = TestTwin.new(OpenStruct.new)
cloned = original.clone
duplicated = original.dup
original.foo = :bar
original.foo #=> :bar
cloned.foo #=> nil
duplicated.foo #=> nil
(I suppose it would be fine if #dup behaved different to #clone, but at least one should achieve the above behaviour and the difference should be documented.)
original.foo #=> :bar
cloned.foo #=> :bar
duplicated.foo #=> :bar
Note that changing the property on either cloned
or duplicated
will likewise modify it on the other two twins as well. The state of the properties is shared among all twins.
Upon subclassing a Twin that defines custom accessors via instance methods, they get overwritten with the stock implementation. To wit:
class ParentTwin < Disposable::Twin
property :my_property
def my_property
"auto-getter overwritten!"
end
end
class ChildTwin < ParentTwin
end
parent_twin = ParentTwin.new(OpenStruct.new)
puts parent_twin.my_property.inspect #=> is "auto-getter overwritten!" as expected
child_twin = ChildTwin.new(OpenStruct.new)
puts child_twin.my_property.inspect #=> is nil, but should be "auto-getter overwritten"
I believe that this is due to the stock accessors being included through a module in https://github.com/apotonick/disposable/blob/master/lib/disposable/twin.rb#L52. As the module inclusion happens after the actual class inheritance, the module's (included) methods overwrite the parent classes (inherited) methods.
This is very counter-intuitive and makes subclassing extremely painful in cases where you want to propagate custom methods down the inheritance chain.
I would suggest either revisiting the design choice of including the getters as a module, or delegating to super
(if present) from within the module.
Thoughts?
The dirty tracking API could be made much more useful with some additional methods being provided. Taking from Sequel:
#column_changes
{name: ['old', 'new']}
#initial_value(column)
'old'
#initial_values
{name: 'old', amount: 0}
#reset_column(column)
obj.name => 'old'
I have a Reform::Form::Module where I'm using unnest. If I try to include it more than once I get an error on startup: gems/disposable-0.4.7/lib/disposable/twin/property/unnest.rb:14:in `unnest': undefined method `[]' for nil:NilClass (NoMethodError)
A setup similar to this:
module Contract::Component
module Foo
include Reform::Form::Module
property :foo do
property :bar
end
unnest :bar, from: :foo
end
end
module Contract
class Create < Reform::Form
include Component::Foo
end
end
module Contract
class Update < Reform::Form
include Component::Foo
end
end
This is the relevant part of the code in disposable
def unnest(name, options)
from = options.delete(:from)
# needed to make reform process this field.
options = definitions.get(from)[:nested].definitions.get(name).instance_variable_get(:@options) # FIXME.
options = options.merge(virtual: true, _inherited: true, private_name: nil)
property(name, options)
delegates from, name, "#{name}="
end
Since unnest is a class method, options.delete(:from)
will actually delete the key from the method argument itself, causing each subsequent call to fail.
Changing that line to from = options[:from]
seems to fix the issue for me, without introducing any noticeable side-effects.
A number of my models use Carrierwave for image uploads, I've just started seeing these errors when saving an instance through an operation:
NoMethodError: undefined method `sync!' for #<ImageUploader:0x007f8fb9263388>
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/disposable-e0c4282118d0/lib/disposable/twin/sync.rb:23:in `block (2 levels) in sync!'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/disposable-e0c4282118d0/lib/disposable/twin/property_processor.rb:34:in `property!'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/disposable-e0c4282118d0/lib/disposable/twin/property_processor.rb:15:in `call'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/disposable-e0c4282118d0/lib/disposable/twin/sync.rb:23:in `block in sync!'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/disposable-e0c4282118d0/lib/disposable/twin/representer.rb:40:in `block in each'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/disposable-e0c4282118d0/lib/disposable/twin/representer.rb:34:in `each'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/disposable-e0c4282118d0/lib/disposable/twin/sync.rb:20:in `sync!'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/disposable-e0c4282118d0/lib/disposable/twin/sync.rb:11:in `sync_models'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/disposable-e0c4282118d0/lib/disposable/twin/save.rb:5:in `save'
/Users/kevinansfield/code/rails/competio-app/app/concepts/horse/crud.rb:44:in `block in process'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/trailblazer-c077c6579a7f/lib/trailblazer/operation.rb:111:in `validate'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/trailblazer-c077c6579a7f/lib/trailblazer/operation/crud.rb:43:in `validate'
/Users/kevinansfield/code/rails/competio-app/app/concepts/horse/crud.rb:43:in `process'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/trailblazer-c077c6579a7f/lib/trailblazer/operation.rb:66:in `run'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/trailblazer-c077c6579a7f/lib/trailblazer/operation.rb:23:in `run'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/trailblazer-c077c6579a7f/lib/trailblazer/operation/controller.rb:42:in `block in run'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/trailblazer-c077c6579a7f/lib/trailblazer/operation/controller.rb:73:in `operation!'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/bundler/gems/trailblazer-c077c6579a7f/lib/trailblazer/operation/controller.rb:42:in `run'
/Users/kevinansfield/code/rails/competio-app/app/controllers/horses_controller.rb:10:in `create'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/action_controller/metal/implicit_render.rb:4:in `send_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/abstract_controller/base.rb:198:in `process_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/action_controller/metal/rendering.rb:10:in `process_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/abstract_controller/callbacks.rb:20:in `block in process_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/callbacks.rb:117:in `call'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/callbacks.rb:117:in `call'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/callbacks.rb:555:in `block (2 levels) in compile'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/callbacks.rb:505:in `call'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/callbacks.rb:505:in `call'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/callbacks.rb:92:in `_run_callbacks'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/callbacks.rb:776:in `_run_process_action_callbacks'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/callbacks.rb:81:in `run_callbacks'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/abstract_controller/callbacks.rb:19:in `process_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/action_controller/metal/rescue.rb:29:in `process_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/notifications.rb:164:in `block in instrument'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activesupport-4.2.1/lib/active_support/notifications.rb:164:in `instrument'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/action_controller/metal/instrumentation.rb:30:in `process_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/action_controller/metal/params_wrapper.rb:250:in `process_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.1/lib/active_record/railties/controller_runtime.rb:18:in `process_action'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/abstract_controller/base.rb:137:in `process'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionview-4.2.1/lib/action_view/rendering.rb:30:in `process'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/action_controller/test_case.rb:632:in `process'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/action_controller/test_case.rb:65:in `process'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/devise-3.5.1/lib/devise/test_helpers.rb:19:in `block in process'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/devise-3.5.1/lib/devise/test_helpers.rb:72:in `catch'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/devise-3.5.1/lib/devise/test_helpers.rb:72:in `_catch_warden'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/devise-3.5.1/lib/devise/test_helpers.rb:19:in `process'
/Users/kevinansfield/.rvm/gems/ruby-2.2.2/gems/actionpack-4.2.1/lib/action_controller/test_case.rb:514:in `post'
/Users/kevinansfield/code/rails/competio-app/test/controllers/horses_controller_test.rb:70:in `block (4 levels) in <class:HorsesControllerTest>'
This was working last week, I'll see if I can track down when exactly it started throwing this error.
Even if just as an alias it would be nice.
class CartTwin < ...
relationship :items
end
I know you want to be agnostic, but we're still talking about wrapping models and most models I've seen (mongoid, activemodel, activerecord, etc) have relationships.
Something expects self.model_name
, which is kinda annoying to handle.
It'd be really awesome if I could, as a feature, dump in ActiveModel
or ActiveRecord
assumptions (table_name
is another).
This causes methods to not be found, kinda annoying.
It would be more readable if you were able to do something like
on :something do
property :blah
# ... x100
end
instead of
property :blah, on: :something
# ... x100
Seems like I'm repeating on: :something
all day long. I'd make a PR for that - if thats something you'd happily see join the code base.
Hi! I don't know if this is intended, but at the very least it might be misleading. It turns out that if you pass a hash of options to Collection#find_by
, it does not actually filter the returned records by all the options, but rather only looks at the first key/value pair of options. This led to a (potential and possibly unimportant) bug in some code someone on my team had written, because the find_by
method looks like it should behave like ActiveRecord's method, but it does not.
It might be good to at least document this behavior, if it's not desirable to change it right now. Thanks!
Suppose I have a Twin A
that contains a collection of Twins B
. Twin B
is backed by a database table. Collection membership is indicated by a twin_a_id
column on the Twin B
database table. That column has a not-null constraint as no Twin B
should ever exist that isn't associated with a Twin A
.
Given the above, I can't remove a Twin B
from the collection. Twin::Collection
always wants to call Twin::Collection#delete
, even when actually using #destroy
. #delete
will try to persist to the database by nullifying twin_a_id
, thus violating the not-null constraint, thus raising an exception.
Perhaps #destroy
should just destroy the associated Twin
(as one might naively expect the name to imply)? Users who actually want to delete, then destroy, would then have to do so manually by calling both methods. Of course this would be a backwards-incompatible change..
Hey Nick, long time no talk, you may not even remember me haha! I've been busy with other stuff and had to pause my work using trailblazer, but I'm back at it now.
Here is the deal, suppose I have the following flat model (no nested objects):
Song = Struct.new(:title, :track, :length)
But from my form I receive this nested hash:
{
song: {
title: 'Roxanne',
details: {
track: 3,
length: '4:10'
}
}
So I have this form that maps nicely to the nested hash:
class SongForm < Reform::Form
property :title
property :details do
property :track
property :length
end
# validations...
end
Now, I don't want to do the mapping nested hash -> flat model
directly in my form, the form should not know about this. So I want introduce a twin that the form will use to do the mapping and syncing, like this:
params = {
song: {
title: 'Roxanne',
details: {
track: 3,
length: '4:10'
}
}
twin = SongTwin.new(Song.new)
form = SongForm.new(twin)
if form.validate(params)
# will sync to the twin and call save on it
# the twin will sync to the model (mapping the nested hash to the flat model) and then call save it
form.save
end
So I need something close to what I can do with nested in Representable (in fact, I stole this example from its documentation), but with the twin getters and setters, since I plan on using the twin to implement simple business logic:
class SongTwin < Disposable::Twin
feature Sync
feature Nested
property :title
nested :details do
property :track
property :length
end
# business logic using the getters and setters (including the nested ones)
end
I believe that currently the simpler way to do this would be to override the sync
method of the twin:
def sync
super do |hash|
model.title = hash[:title]
model.track = hash[:details][:track]
model.length = hash[:details][:length]
end
end
But this is obviously not a nice solution and it doesn't work for reading...
This relates to this Reform issue: trailblazer/reform#277
My code needs to know which model a property is on, after going through the source, I ended up with:
on_model = form.class.representer_class.representable_attrs[:definitions][property_name][:on]
model = form.object.model[on_model]
I'd really like to just do
form.property_definiton(property_name).model
or something
As the title of this issue
when I try to include Disposable::Twin::Struct module
Ruby raises error, because I am trying to include ::Struct class
I know that Disposable::Twin::Struct is deprecated, I should use Disposable::Twin::Property::Struct
but I couldn't require it, maybe because Disposable::Twin::Property::Struct is in the property.rb file
so my gem unable to load it
property :foo do
include Disposable::Twin::Struct
end
property :foo do
include ::Disposable::Twin::Struct
end
all of the code above doesn't work
How to set writeable dynamically? (with a block / lambda or...)
Tried these, all act as writeable: true
no matter what's in the model
module User
module Contract
class Update < Reform::Form
property :nickname, writeable: -> { model.nickname.blank? }
property :address, writeable: model_nickname_blank?
property :phone, writeable: :model_phone_blank
def model_nickname_blank?
model.nickname.blank?
end
def model_phone_blank
model.phone.blank?
end
end
end
end
Thanks!
See:
It's probably related to dry-rb dependency.
Dry-rb has dropped support for old rubies.
Related to: trailblazer/representable#208
I have a populator like this, according to this document
populator:->(collection:, index:, fragment:, **) {
item = codes.find_by(id: fragment["id"])
if fragment['id']
codes.delete(item)
return skip!
end
}
I verified that the #destroy was actually called, but this caused an error:
SQL (1.4ms) DELETE FROM
codes
WHEREcodes
.id
= 14
(2.7ms) ROLLBACK
Completed 422 Unprocessable Entity in 1709ms (ActiveRecord: 23.4ms)
I simplified the table name because of my project's security, but the 'codes' table is the association table of an has_many through association.
I tried to run active record's destroy method in rails console and it did destroy the record from db successfully.
Do you have any idea why this cause the error?
class TradeInConfigurationForm < Disposable::Twin
include Property::Hash
property :options, field: :hash do
collection :active_features, nilify: true
end
end
config = EmbedConfigurations::TradeIn.find(id)
twin = TradeInConfigurationForm.new(config)
twin.options.active_features = ["lead_form", ""]
twin.options.active_features
#=> ["lead_form", ""]
I would expect the output to be ["lead_form"]
I'm actually using Reform but the example here is simplified.
This happens especially when using Rails and submitting collection_check_boxes
through a form (it appends an empty string so that the update works when de-selecting all check boxes).
Is this not supported or is it a bug?
You have to add save: false when using a property that include Struct, if you don't you get an undefined method save for {}:Hash.
Currently, there is no documentation for compositions in the Readme, however looking in the source code, we see that Compositions should be defined as follows
class Album
include Disposable::Composition
map( {cd: [[:id], [:name]], band: [[:id, :band_id], [:title]]} )
end
The current version of the Trailblazer book however, alludes to being able to define Compositions like this
class Comment::Opinion < Disposable::Twin
include Composition
property :body, on: :comment
property :email, on: :author
end
Which I feel is a much nicer DSL
Is the latter the plan for how compositions will work?
class Customer < ApplicationRecord
belongs_to :organization
end
by default Rails validates this association as required
, meaning if you do net set an organization_id
, record creation will fail
Rails default validations should be catched by default
require 'disposable'
class A < Disposable::Twin
class Sub < self
end
property :foo
end
A::Sub.definitions
# => {}
class B < Disposable::Twin
property :foo
class Sub < self
end
end
B::Sub.definitions
# => { "foo" => #<Disposable::Twin::Definition> }
Gemspec does not lock uber at or under a version number. Uber recently updated to v0.1.0 and introduced breaking changes (undefined method class_builder). Took me a little while to track down...
Okay, so the title is a bit screwey, but here's the example:
class CartTwin
collection :redemptions, twin: RedemptionTwin
property :discount_cents
end
class RedemptionTwin
property :cart
end
Okay, everything looks good so far.
cart = Cart.find_by(id: '...')
cart.redemptions.count # => 2
cart.redemptions.map(&:class) # => [Redemption, Redemption]
cart.class # => Cart
twin = CartTwin.new(cart)
twin.redemptions.count # => 2
twin.redemptions.map(&:class) # => [RedemptionTwin, RedemptionTwin]
twin.redemptions.map(&:model).map(&:class) # => [Redemption, Redemption]
twin.class # => CartTwin
twin.model.class # => Cart
Still fine, but now:
shadow = CartTwin.new(twin)
shadow.class # => CartTwin
shadow.model # => Cart
Amazing! Each new subsequent twinning will pick up the correct properties, but know the right model
. Perfect, until...
shadow.redemptions.map(&:class) # => [RedemptionTwin, RedemptionTwin]
shadow.redemptions.map(&:model).map(&:class) # => [RedemptionTwin, RedemptionTwin]
Uh-oh.
shadow.redemptions.map(&:model).map(&:model).map(&:class) # => [Redemption, Redemption]
I've been experimenting today and found that if you have a HABTM association which is set through disposable such as tag_ids
then the underlying active record will save itself when the setter for that attribute is invoked.
This means that calling sync()
on disposable can inadvertently save the underlying record. This would presumably also happen with has_many associations of a similar nature that AR also likes to autosave.
This is a quirk with AR rather than anything specific to Disposable, but after a Gitter conversation with @apotonick he asked me to file this as a reminder to anyone.
The following happens on the Rails console:
require "disposable/twin/parent"
class TestForm < Reform::Form
feature Disposable::Twin::Parent
end
# => NoMethodError: private method `property' called for TestForm:Class
Here's a vanilla Rails 4.2.8 app bundling "reform" and "reform-rails" (both from master) to showcase the problem:
property :is_admin, virtual: true, default: "0"
# then:
twin.is_admin #=> Uber::Options::Value
I realize #scopes
is a feature specific to ActiveRecord and this library is pretty agnostic. Here's an example:
class Cart
has_many :items
end
class Item
belongs_to :cart
scope :nikes, -> { where(brand: "nike") }
end
cart.items.nikes
With this library it raises an error:
NoMethodError: undefined method `nikes' for #<Disposable::Twin::Collection:...>
I'm not sure how it would work, but I would love to see this:
class ItemTwin < Disposable::Twin
property :brand
property :cart
subset :nikes, twin: ItemTwin
end
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.