New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for modules with attributes #90
Merged
Merged
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
5600671
Initial work on Virtus modules
solnic fb30a55
No idea why I added reverse there
solnic e9ed8d8
Make Virtus work with classes, instances and modules (messy spike com…
solnic 6f5924e
Merge branch 'master' into modules-spike
solnic 8f3c2a6
Add more use cases to the using modules integration spec
solnic 6f6e96e
Rename ClassExtensions to ClassInclusions
dkubb 7e30658
Remove AllowedWriterMethods
solnic 76e6a35
Move allowed_writer_methods down a bit
solnic e256f84
Remove unecessary line from protected method
dkubb d81ac7d
Change methods to be private
dkubb e01b2d1
Add @api private doc to public_method_list
dkubb 342cb26
Fix whitespace
dkubb 371c2c9
Rename _attributes to attribute_set
solnic 6d03269
Merge branch 'master' into modules-spike
solnic fe492ed
be kind and call super in ModuleExtensions#extended and #included to …
apotonick b5f27a5
Merge pull request #92 from apotonick/modules-spike
solnic 04f8567
Fix Virtus.extended visibility
solnic 023fdee
Rename InstanceExtensions to just Extensions
solnic 76acc2d
Fix visibility of Extensions.extended
solnic 57706fa
Adjust flay threshold
solnic 7ce6f99
Raise argument error if Virtus is being included into an unsupported …
solnic 3615ab4
Go back to 100% doc coverage
solnic 15ad0b5
Simplify Virtus.included (that case statement made no sense)
solnic File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module Virtus | ||
module ClassInclusions | ||
|
||
def self.included(descendant) | ||
super | ||
descendant.extend(ClassMethods) | ||
descendant.extend(InstanceExtensions::AllowedWriterMethods) | ||
descendant.send(:include, InstanceMethods) | ||
end | ||
|
||
def _attributes | ||
self.class.attributes | ||
end | ||
|
||
def allowed_writer_methods | ||
self.class.allowed_writer_methods | ||
end | ||
|
||
end # module ClassInclusions | ||
end # module Virtus |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
module Virtus | ||
|
||
# Instance-level extensions | ||
module InstanceExtensions | ||
module AllowedWriterMethods | ||
WRITER_METHOD_REGEXP = /=\z/.freeze | ||
INVALID_WRITER_METHODS = %w[ == != === []= attributes= ].to_set.freeze | ||
|
||
# The list of writer methods that can be mass-assigned to in #attributes= | ||
# | ||
# @return [Set] | ||
# | ||
# @api private | ||
def allowed_writer_methods | ||
@allowed_writer_methods ||= | ||
begin | ||
allowed_writer_methods = public_method_list.map(&:to_s) | ||
allowed_writer_methods = allowed_writer_methods.grep(WRITER_METHOD_REGEXP).to_set | ||
allowed_writer_methods -= INVALID_WRITER_METHODS | ||
allowed_writer_methods.freeze | ||
end | ||
end | ||
end | ||
|
||
def self.extended(object) | ||
object.extend(AllowedWriterMethods) | ||
object.extend(InstanceMethods) | ||
object.instance_eval do | ||
@virtus_attributes_accessor_module = AttributesAccessor.new(object.class.inspect) | ||
extend @virtus_attributes_accessor_module | ||
end | ||
end | ||
|
||
# Defines an attribute on an object's class | ||
# | ||
# @example | ||
# class Book | ||
# include Virtus | ||
# | ||
# attribute :title, String | ||
# attribute :author, String | ||
# attribute :published_at, DateTime | ||
# attribute :page_count, Integer | ||
# end | ||
# | ||
# @param [Symbol] name | ||
# the name of an attribute | ||
# | ||
# @param [Class] type | ||
# the type class of an attribute | ||
# | ||
# @param [#to_hash] options | ||
# the extra options hash | ||
# | ||
# @return [self] | ||
# | ||
# @see Attribute.build | ||
# | ||
# @api public | ||
def attribute(*args) | ||
attribute = Attribute.build(*args) | ||
attribute.define_accessor_methods(@virtus_attributes_accessor_module) | ||
virtus_add_attribute(attribute) | ||
self | ||
end | ||
|
||
def _attributes | ||
@_attributes ||= AttributeSet.new | ||
end | ||
|
||
def virtus_add_attribute(attribute) | ||
_attributes << attribute | ||
end | ||
end # module InstanceExtensions | ||
end # module Virtus |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
module Virtus | ||
|
||
# Virtus module class that can define attributes for later inclusion | ||
# | ||
module ModuleExtensions | ||
|
||
def extended(object) | ||
object.extend(Virtus) | ||
define_attributes(object) | ||
end | ||
|
||
def included(object) | ||
object.send(:include, ClassInclusions) | ||
define_attributes(object) | ||
end | ||
|
||
def attribute(*args) | ||
attribute_definitions << args | ||
end | ||
|
||
private | ||
|
||
def attribute_definitions | ||
@_attribute_definitions ||= [] | ||
end | ||
|
||
def define_attributes(object) | ||
attribute_definitions.each do |attribute_args| | ||
object.attribute(*attribute_args) | ||
end | ||
end | ||
|
||
end # class Module | ||
end # module Virtus |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
require 'spec_helper' | ||
|
||
describe 'I can extend objects' do | ||
before do | ||
module Examples | ||
class User; end | ||
|
||
class Admin; end | ||
end | ||
end | ||
|
||
specify 'defining attributes on an object' do | ||
attributes = { :name => 'John', :age => 29 } | ||
|
||
admin = Examples::Admin.new | ||
admin.extend(Virtus) | ||
|
||
admin.attribute :name, String | ||
admin.attribute :age, Integer | ||
|
||
admin.name = 'John' | ||
admin.age = 29 | ||
|
||
admin.name.should eql('John') | ||
admin.age.should eql(29) | ||
|
||
admin.attributes.should eql(attributes) | ||
|
||
new_attributes = { :name => 'Jane', :age => 28 } | ||
admin.attributes = new_attributes | ||
|
||
admin.name.should eql('Jane') | ||
admin.age.should eql(28) | ||
end | ||
end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@solnic do you think we can just do this here:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dkubb we need that ivar, it's used in #attribute. so maybe
object.instance_variable_set
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@solnic ahh yeah, after I mentioned it, I realized that the object needs the ivar set internally. I think
#instance_eval
the way we're doing it is probably nicer then.