Coder Social home page Coder Social logo

danielpclark / polybelongsto Goto Github PK

View Code? Open in Web Editor NEW
46.0 8.0 5.0 197 KB

Uniform Omni-Relational ActiveRecord Methods

License: MIT License

Ruby 99.67% HTML 0.33%
polymorphic-relations record-duplication activerecord metaprogramming database integrity ruby-gem

polybelongsto's Introduction

PolyBelongsTo

Gem Version Code Climate Build Status Test Coverage Inline docs Featured In Ruby Weekly #233 Featured In Green Ruby #118 SayThanks.io

Original Purpose: A standard way to check belongs_to relations on any belongs_to Object and let you check your DB Objects polymorphism in a more across-the-board meta-programatically friendly way.

PolyBelongsTo has grown into a powerful tool for working with all kinds of ActiveRecord relationships and situations. PBT makes handling things in AR easier to deal with in a more generic way. There are also some hierarchal tools provided which make coding with AR relationships all the more powerful. See anything that's missing? Please open an issue and suggest a feature!

Database integrity can be checked with a few methods to find orphaned record relations and incorrectly typed polymorphic relations.

Deep cloning of records with associations is an added feature this gem provides. It requires no configuration to use, you can simply invoke: pbt_deep_dup_build. This makes for much easier record duplication than the deep_cloneable gem.

Installation

Just include it in your Gemfile and then run bundle:

gem 'poly_belongs_to', '~> 1.0'

Recommended Usage

On model class
# Is Polymorphic?
MyOject.poly?
# => true
User.poly?
# => false
    
# Belongs To Relation Table
MyObject.pbt
# => :my_objectable
User.pbt
# => nil 

# Multiple Belongs To Relations
Tire.pbts
# => [:user, :car]

# Multiple Has One Relations
Profile.has_one_of
# => [:photo]

# Multiple Has Many Relations
Profile.has_many_of
# => [:phones, :addresses]

# Multiple Has And Belongs To Many Relations
Assembly.habtm_of
# => [:parts]

# Get orphaned objects for records of class type
MyObject.pbt_orphans
# => #<ActiveRecord::Relation []> # nil for objects without belongs_to

# Get polymorphic objects for records of invalid class type(s)
MyObject.pbt_mistyped
# => #<ActiveRecord::Relation []> 

# Get the invalid class types on polymorphic records as a Array of Strings
MyObject.pbt_mistypes
# => ["Object", "Class", "MyObjectable"]

# Get the valid class types on polymorphic records as a Array of Strings
MyObject.pbt_valid_types
# => ["User", "MyObject"]

# Params name
MyObject.pbt_params_name
# => :my_objectable_attributes
MyObject.pbt_params_name(false)
# => :my_object
User.pbt_params_name
# => :user

# DB column names
MyObject.pbt_id_sym             
# => :my_objectable_id
MyObject.pbt_type_sym           
# => :my_objectable_type        # nil for non polymorphic Objects
On model instances
# Belongs To Relations ID
MyObject.first.pbt_id
# => 123
    
# Polymorphic Belongs To Relations Type
MyObject.first.pbt_type
"User"                          # nil for non polymorphic Objects

# Get Parent Object (Works on all belongs_to Objects)
MyObject.first.pbt_parent
# => #<User id: 123 ... >

# Get Top Hierarchical Parent Object (Works on all belongs_to Objects)
MyObject.first.pbt_top_parent
# => #<User id: 123 ... >

# Mutliple Parent Objects (List of one item for Polymorphic, full list otherwise.)
Tire.first.pbt_parents
# => [#<User id: 123 ... >, #<Car id: 234 ... >]

# Determine if object is orphaned (parent no longer exists)
Tire.first.orphan?
# => false

Also Available

# --- Model Instances ---
# NOTE: touches db if object isn't already instantiated

# Is Polymorphic?
MyObject.new.poly?
# => true
User.first.poly?
# => false

# Belongs To Relation Table
MyObject.new.pbt
# => :my_objectable
User.first.pbt
# => nil

# Multiple Belongs To Relations
Tire.first.pbts
# => [:user, :car]

# Params name
MyObject.new.pbt_params_name
# => :my_objectable_attributes
MyObject.new.pbt_params_name(false)
# => :my_object
User.first.pbt_params_name
# => :user

# DB column names
MyObject.new.pbt_id_sym
# => :my_objectable_id
MyObject.new.pbt_type_sym       # nil for non polymorphic Objects
# => :my_objectable_type

Internal Methods Available

# For cleaning attributes for use with build
PolyBelongsTo::Pbt::AttrSanitizer[ obj ]

# Returns string of either 'child.build' or 'build_child'
PolyBelongsTo::Pbt::BuildCmd[ obj, child ]

# Returns has_one and has_many relationships for obj as an Array of symbols
PolyBelongsTo::Pbt::Reflects[ obj, habtm = false ]

# Returns Class Ojects for each has_one and has_many child associations
PolyBelongsTo::Pbt::ReflectsAsClasses[ obj, habtm = false ]

# Boolean of whether child object/class is a has(one/many) relationship to obj
PolyBelongsTo::Pbt::IsReflected[ obj, child ]

# Returns :singular if obj->child is has_one and :plural if obj->child is has_many
PolyBelongsTo::Pbt::SingularOrPlural[ obj, child ]

# Returns true if obj->child relationship is has_one
PolyBelongsTo::Pbt::IsSingular[ obj, child ]

# Returns true if obj->child relationship is has_many
PolyBelongsTo::Pbt::IsPlural[ obj, child ]

# Returns the symbol for the CollectionProxy the child belongs to in relation to obj
# NOTE: For has_one the symbol is not a CollectionProxy, but represents the instance
PolyBelongsTo::Pbt::CollectionProxy[ obj, child ]

# Always returns a collection proxy; fakes a collection proxy for has_one
PolyBelongsTo::Pbt::AsCollectionProxy[ obj, child ]

# Wrapper for has_one objects to be a collection proxy
PolyBelongsTo::FakedCollection.new(obj, child)

# Track which DB records have already been processed
PolyBelongsTo::SingletonSet.new

In methods that have more than one type of ownership the order or precedence is polymorphic relationships first, primary key next (or first reflection in lookup).

Record Duplication

This gives you the advantage of duplicating records regardless of polymorphic associations or otherwise. You can duplicate a record, or use a self recursive command pbt_deep_dup_build to duplicate a record and all of it's has_one/has_many children records at once. Afterwards be sure to use the save method.

Known Issues

  • Carrierwave records won't duplicate. To ensure that other records will still save and prevent any rollback issues use .save(validate: false) ... I'm considering possible options to remedy this and other scenarios.

How To Use

Use the dup/build methods as follows

# If you were to create a new contact for example
contact = User.first.contacts.new

# This is just like contact.profile.build( { ... user's profile attributes ... } )
contact.pbt_dup_build( User.last.profile )

# Save it!
contact.save


# For a fully recursive copy do the same with pbt_deep_dup_build
contact.pbt_deep_dup_build( User.last.profile )

# Remeber to save!
contact.save

Contributing

Feel free to fork and make pull requests. Please bring up an issue before a pull request if it's a non-fix change. Please add applicable fixtures and tests for any new features/implementations you add.

Thank You!

License

The MIT License (MIT)

Copyright (C) 2015-2017 by Daniel P. Clark

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

polybelongsto's People

Contributors

chriskottom avatar danielpclark avatar tombroomfield avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

polybelongsto's Issues

duplication breaks during has_one on a has_many polymorphic child

I added a fixture geo_location to address with a has_one relationship I'm getting the following error:

ArgumentError: wrong number of arguments (1 for 2)
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:15:in `pbt_deep_dup_build'
/home/user/.rvm/gems/ruby-2.2.0/gems/activerecord-4.0.13/lib/active_record/relation/delegation.rb:60:in `block in method_missing'
/home/user/.rvm/gems/ruby-2.2.0/gems/activerecord-4.0.13/lib/active_record/associations/collection_proxy.rb:849:in `block in scoping'
/home/user/.rvm/gems/ruby-2.2.0/gems/activerecord-4.0.13/lib/active_record/relation.rb:270:in `scoping'
/home/user/.rvm/gems/ruby-2.2.0/gems/activerecord-4.0.13/lib/active_record/associations/collection_proxy.rb:849:in `scoping'
/home/user/.rvm/gems/ruby-2.2.0/gems/activerecord-4.0.13/lib/active_record/relation/delegation.rb:60:in `method_missing'
/home/user/.rvm/gems/ruby-2.2.0/gems/activerecord-deprecated_finders-1.0.3/lib/active_record/deprecated_finders/collection_proxy.rb:22:in `method_missing'
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:25:in `block in pbt_deep_dup_build'
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:17:in `each'
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:17:in `pbt_deep_dup_build'
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:39:in `pbt_deep_dup_build'
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:22:in `block (2 levels) in pbt_deep_dup_build'
/home/user/.rvm/gems/ruby-2.2.0/gems/activerecord-4.0.13/lib/active_record/relation/delegation.rb:13:in `each'
/home/user/.rvm/gems/ruby-2.2.0/gems/activerecord-4.0.13/lib/active_record/relation/delegation.rb:13:in `each'
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:21:in `block in pbt_deep_dup_build'
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:17:in `each'
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:17:in `pbt_deep_dup_build'
/home/user/dev/PolyBelongsTo/lib/poly_belongs_to/dup.rb:39:in `pbt_deep_dup_build'
/home/user/dev/PolyBelongsTo/test/dup_test.rb:31:in `block in <class:DupTest>'

I'm working on resolving this issue now.

Gem builds include log files from test runs

Notice that the installed gem was taking nearly 50MB and it turns out that you're including the log files from the dummy app when building the gem - probably want to strip those out ๐Ÿ˜‰

SingletonSet should maintain Set behavior

Revert behavior on add and << methods. Change method_missing to take args & blocks and map any args that are ActiveRecord Objects to fromatted_name(record) before forwarding to inner @set.

pbt_parent fails with camel case model names?

So I have a model WorkOrder and in my work_order.rb which contains:

has_many :events, dependent: :destroy

and anEvents model and my event.rb which contains:

belongs_to :work_order

So in this example this is NOT a polymorphic relationship but PolyBelongsTo is supposed to work for any relationship and in my code if I call:

Event.last.pbt_parent I get:

LoadError: Unable to autoload constant Work_order, expected ../work_order.rb to define it

I am using PBT to figure out the parents of many of my models. I have added a work around for this one for now. Perhaps its not the camel case but it's just a hunch.

Rails 6 compatibility?

Hi @danielpclark! Thanks for creating this library! Curious if this is compatible with Rails 6? If not, is that on the radar? Seems like the Gemspec explicitly wants version less than Rails 6.

Hierarchy implementation and design

I'd like to map each record with the same deep duplication method

PolyBelongsTo::Dup::pbt_deep_dup_build

It will have an additional paramenter

@hierarchy_registrar = nil

The first line in the method should be something like

@hierarchy_registrar ||= PolyBelongsTo::Hierarchy.registrar

PolyBelongsTo::Hierarchy.registrar will be a registry that gets accessed and will asses if the record already exists within the current hierarchy. If so it will return an appropriate response. The deep duplication method will treat that particular hierarchal progression as an end node and won't duplicate the existing record. It won't even worry about pointers. It will just treat the existing "in-house" record as a "no-record". In other words the existing duplication will work the same but this will stop once it reaches a record it has already duplicated.

Get tests running with Rails 3.1, 3.2, and 4.0 on Travis CI.

Locally tests pass on Rails 4.0. On Travis CI the fixtures fail to load.

Rails 3.1 and 3.2 on the other hand seem to require the test-unit gem. Once that is included the existing minitest tests fail to run.

Considering using something like WWTD gem to speed up test cycles locally as per @chriskottom's suggestion.

Simplify pbt_deep_dup_build conditional checks

This block of code:

if child.respond_to?(:build)
  child.each do |spawn|
    if core.respond_to?(:build)
      core.each do |subscore|
        subscore.pbt_deep_dup_build(spawn)
      end
    else
      core.pbt_deep_dup_build(spawn)
    end
  end
else
  if core.respond_to?(:build)
    core.each do |subcore|
      subcore.pbt_deep_dup_build(child)
    end
  else
    core.pbt_deep_dup_build(child)
  end
end

Needs to be refactored into something much simpler. It works, but it shouldn't have to be this excessive.

Order of belongs_to in model effects results

I am not sure if this is a bug or intentional. I have a simple polymorphic Comment model:

create_table "comments", force: :cascade do |t|
t.integer "user_id"
t.integer "commentable_id"
t.string "commentable_type"
t.text "note"
t.string "ancestry"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

and my comment.rb file:

belongs_to :user
belongs_to :commentable, polymorphic: true

If I call Comment.last.poly? I get false and Comment.last.pt_parent results in the user from user_id.

When I switch it to:

belongs_to :commentable, polymorphic: true
belongs_to :user

I get true and the actual polymorphic parent resource. This is actually what I expected and tried this from the model on a hunch. The biggest issue here I see is that if you don't know this or change your model down the road you could easily break things.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.