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
- Fork it (https://github.com/panorama-ed/rails_external_fields/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - 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.