Giter VIP home page Giter VIP logo

rails_external_fields's Introduction

Code Coverage Build Status Inline docs Gem Version

ExternalFields

Create the illusion that an object has specific attributes when those attributes actually belong to an associated object.

This is particularly useful for different classes within a single- table inheritance table to have access to separate fields in class-specific associations.

Installation

Add this line to your application's Gemfile:

gem "external_fields"

And then execute:

$ bundle

Or install it yourself as:

$ gem install external_fields

Usage

Include ExternalFields and define the external fields using the external_field method. For example, if grade_level, age and credits are defined in another class StudentData and you want to access them in Student you could do:

require "active_record"
require "active_support"

require "external_fields"

class Student < ActiveRecord::Base
  include ExternalFields

  has_one :data,
          class_name: StudentData

  external_field :grade_level,              # External attribute 1
                 :age,                      # External attribute 2
                 :credits,                  # External attribute 3
                 :data,                     # Name of the association
                 class_name: "StudentData", # Class name of association
                 save_empty: false          # Don't save empty associations
end

where the external fields are defined in another associated class:

class StudentData < ActiveRecord::Base
  attr_accessor :grade_level, :age, :credits
end

Now you can directly call the accessors on the Student objects:

 > s = Student.create!
 > s.age
=> nil

 > s.age = 10
 > s.age
=> 10

 > s.grade_level = 4
 > s.grade_level
=> 4

Overriding default behavior using underscored accessors

You can also add underscored accessors using the underscore flag

...
  external_field :grade_level,             # External attribute 1
                 :age,                     # External attribute 2
                 :credits,                 # External attribute 3
                 :data,                    # Name of the association
                 class_name: "StudentData" # Class name of association
                 underscore: true          # Flag for underscored accessors
...

This will allow you to use the external fields using underscored methods:

s = Student.create!
s._age
s._grade_level

This approach lets you override the default behavior cleanly. For example, you could override the grade level using this method:

def grade_level
  if _grade_level == 0
    "Kindergarten"
  else
    _grade_level
  end
end

Overriding default behavior using save_empty: false

This is the recommended configuration to use for all new code.

To avoid unnecessary writes, you can rely on empty-valued class instances so that external associations are only saved when they have one or more attributes with non-default values.

For any given association class, its constructor defines the attribute values for an "empty" instance. This means that, in the below example, retreival of data will return StudentData.new if there's no StudentData record saved. If set_empty: true were configured instead, calling data would still return StudentData.new, but it would also write the empty record to the database.

  external_field :grade_level,              # External attribute 1
                 :age,                      # External attribute 2
                 :credits,                  # External attribute 3
                 :data,                     # Name of the association
                 class_name: "StudentData", # Class name of association
                 save_empty: false          # Don't save empty associations

The default value for save_empty is true only for backward compatability, as existing code using this gem may rely on empty rows existing in a database.

Accessing the original association

In some instances it's helpful to be able to use the original association without building an object on access. For instance, you might want to have a validation inspect a value without creating a new object on each save. In that case, you can use the use_original flag on the association like so:

validate :kindergarten_students_have_names

def kindergarten_students_have_names
  data_obj = data(use_original: true)

  if data_obj && grade_level == "Kindergarten" && name.blank?
    # Note that `name` is an attribute on `Student` but `grade_level`
    # is accessed through the `data` association as defined earlier
    # in the README.
    errors.add(:name, "must be present for kindergarten students")
  end
end

Documentation

We have documentation on RubyDoc.

Contributing

  1. Fork it (https://github.com/panorama-ed/rails_external_fields/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Make sure your changes have appropriate tests (bundle exec rspec) and conform to the Rubocop style specified. We use overcommit to enforce good code.

License

ExternalFields is released under the MIT License.

rails_external_fields's People

Contributors

ch-panorama avatar dependabot-preview[bot] avatar epbarger avatar estern1011 avatar honigc avatar jacobevelyn avatar jordannb avatar nancycat97 avatar ngnasr1123 avatar parkerfinch avatar sagarjauhari avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 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

rails_external_fields's Issues

Inconsistent set via associated record name behavior with `save_empty: false`

See tests defined here, noting that only the second one fails: jordannb@78c2e8d

For convenience, here is the failing test:

it "does save non-empty values when using assoc name" do
  e = TestClass.create!
  e.name = "TEST"
  e.no_empties_assoc.ext_field_using_empties_not_saved = "Another test"
  e.save!
  expect(NoEmptiesAssociationTestClass.count).to eq(1)
end

This fails because the empty record returned by no_empties_assoc is merely an in-memory placeholder and isn't actually associated (in ActiveRecord terms) with the parent record, e, that it "belongs" to. Data set this way will work if the :no_empties_assoc returns a non-empty value (on that is stored in the database), as this means there's an association with the parent record.

Due to the custom setters created by the ExternalFields gem, we don't actually need to set values using this mechanism at all, and the fact that it works is an undocumented behavior (note that README and existing tests suggest that e.ext_field_using_empties_not_saved = "Another test" is the way to set values, not e.no_empties_assoc.ext_field_using_empties_not_saved = "Another test". The second mechanism is redundant, undocumented, and perhaps unnecessary.

I propose we either update the behavior so that it's consistent or remove the ability to set values in this way entirely. It'd be good to consider addressing this before #29 .

Dependabot couldn't fetch the branch/reference for panolint

Your dependency file specified a branch or reference for panolint, but Dependabot couldn't find it at the project's source. Has it been removed?

For Ruby dependencies, this can be caused by a branch specified in your Gemfile being deleted at the source, or having been rebased, so the commit reference in your Gemfile.lock is no longer included in the branch. In that case, it can be fixed by running bundler update panolint locally.

View the update logs.

Race condition when creating external fields object

This is concurrency issue. If two processes try to access an external field at the same time, they will both try to create the external field object. We should prevent this for reads, because it will just create an empty object, and that seems unnecessary.

Redundancy between `use_original: true` and `save_empty: false`

use_original: true and save_empty: false provide near-duplicate behavior, allowing the value of a non-existent associated record to be retrieved without resulting in a write of an empty record. The only meaningful difference is that use_original: true causes a return value of nil when that association record is retrieved, and save_empty: false causes an "empty" record in that same scenario. This means that with use_original: true, empty values are more easily detected by the consumer, but with save_empty: false, values can be set on the associated record without needing to explicitly initialize an associated record.

use_original: true should be removed, and save_empty: false should be made the default behavior (no config option needed). If necessary, The "empty" value could be made configurable so that an empty record can be indicated by nil instead of an empty object instance as needed (just like with the use_original: true behavior).

Approve this gem to be made public

Check that:

  • All code is as general as possible
  • Code has as few dependencies as possible
  • Gemspec has overcommit and rubocop (and any other needed linters) as development dependencies
  • License is attributed to Panorama Education
  • Tests are reasonable
  • All code is commented with YARD style
  • This gem is something that should be public (is not a security liability, contains no Panorama-specific code, does not make Panorama look bad, etc.)

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.