Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Reinier de Lange
committed
Jul 28, 2010
1 parent
1e45a59
commit 20bb562
Showing
10 changed files
with
376 additions
and
9 deletions.
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
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,52 @@ | ||
# Generated by jeweler | ||
# DO NOT EDIT THIS FILE DIRECTLY | ||
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command | ||
# -*- encoding: utf-8 -*- | ||
|
||
Gem::Specification.new do |s| | ||
s.name = %q{dynamic_attributes} | ||
s.version = "0.0.0.pre1" | ||
|
||
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version= | ||
s.authors = ["Reinier de Lange"] | ||
s.date = %q{2010-07-29} | ||
s.description = %q{Dynamic attributes is a gem that lets you dynamically specify attributes on ActiveRecord models, which will be serialized and | ||
deserialized to a given text column. Dynamic attributes can be defined by simply setting an attribute or by passing them on create or update.} | ||
s.email = %q{r.j.delange@nedforce.nl} | ||
s.extra_rdoc_files = [ | ||
"LICENSE", | ||
"README.rdoc" | ||
] | ||
s.files = [ | ||
".document", | ||
".gitignore", | ||
"LICENSE", | ||
"README.rdoc", | ||
"Rakefile", | ||
"VERSION", | ||
"init.rb", | ||
"lib/dynamic_attributes.rb" | ||
] | ||
s.homepage = %q{http://github.com/moiristo/dynamic_attributes} | ||
s.rdoc_options = ["--charset=UTF-8"] | ||
s.require_paths = ["lib"] | ||
s.rubygems_version = %q{1.3.6} | ||
s.summary = %q{Dynamic attributes is a gem that lets you dynamically specify attributes on ActiveRecord models, which will be serialized and deserialized to a given text column.} | ||
s.test_files = [ | ||
"test/helper.rb", | ||
"test/test_dynamic_attributes.rb", | ||
"test/database.yml", | ||
"test/schema.rb" | ||
] | ||
|
||
if s.respond_to? :specification_version then | ||
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION | ||
s.specification_version = 3 | ||
|
||
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then | ||
else | ||
end | ||
else | ||
end | ||
end | ||
|
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 @@ | ||
require 'dynamic_attributes' |
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,118 @@ | ||
|
||
# Adds the has_dynamic_attributes method in ActiveRecord::Base, which can be used to configure the module. | ||
class << ActiveRecord::Base | ||
|
||
# Method to call in AR classes in order to be able to define dynamic attributes. The following options can be defined: | ||
# | ||
# * :dynamic_attribute_field - Defines the attribute to which all dynamic attributes will be serialized. Default: :dynamic_attributes. | ||
# * :dynamic_attribute_prefix - Defines the prefix that a dynamic attribute should have. All assignments that start with this prefix will become | ||
# dynamic attributes. Note that it's not recommended to set this prefix to the empty string; as every method call that falls through to method_missing | ||
# will become a dynamic attribute. Default: 'field_' | ||
# * :destroy_dynamic_attribute_for_nil - When set to true, the module will remove a dynamic attribute when its value is set to nil. Defaults to false, causing | ||
# the module to store a dynamic attribute even if its value is nil. | ||
# | ||
def has_dynamic_attributes(options = { :dynamic_attribute_field => :dynamic_attributes, :dynamic_attribute_prefix => 'field_', :destroy_dynamic_attribute_for_nil => false}) | ||
cattr_accessor :dynamic_attribute_field | ||
self.dynamic_attribute_field = options[:dynamic_attribute_field] || :dynamic_attributes | ||
cattr_accessor :dynamic_attribute_prefix | ||
self.dynamic_attribute_prefix = options[:dynamic_attribute_prefix] || 'field_' | ||
cattr_accessor :destroy_dynamic_attribute_for_nil | ||
self.destroy_dynamic_attribute_for_nil = options[:destroy_dynamic_attribute_for_nil] || false | ||
|
||
include DynamicAttributes | ||
end | ||
end | ||
|
||
# The DynamicAttributes module handles all dynamic attributes. | ||
module DynamicAttributes | ||
|
||
# On saving an AR record, the attributes to be persisted are re-evaluated and written to the serialization field. | ||
def before_save | ||
new_dynamic_attributes = {} | ||
self.persisting_dynamic_attributes.uniq.each do |dynamic_attribute| | ||
value = send(dynamic_attribute) | ||
if value.nil? and destroy_dynamic_attribute_for_nil | ||
self.persisting_dynamic_attributes.delete(dynamic_attribute) | ||
singleton_class.send(:remove_method, dynamic_attribute + '=') | ||
else | ||
new_dynamic_attributes[dynamic_attribute] = value | ||
end | ||
end | ||
write_attribute(self.dynamic_attribute_field, new_dynamic_attributes) | ||
end | ||
|
||
# After find, populate the dynamic attributes and create accessors | ||
def after_find | ||
(read_attribute(self.dynamic_attribute_field) || {}).each {|att, value| set_dynamic_attribute(att, value); self.destroy_dynamic_attribute_for_nil = false if value.nil? } | ||
end | ||
|
||
# Creates an accessor when a non-existing setter with the configured dynamic attribute prefix is detected. Calls super otherwise. | ||
def method_missing(method, *arguments, &block) | ||
(method.to_s =~ /#{self.dynamic_attribute_prefix}(.+)=/) ? set_dynamic_attribute(self.dynamic_attribute_prefix + $1, *arguments.first) : super | ||
end | ||
|
||
# Overrides the initializer to take dynamic attributes into account | ||
def initialize(attributes = nil) | ||
dynamic_attributes = {} | ||
attributes.each{|att,value| dynamic_attributes[att] = value if att.to_s.starts_with?(self.dynamic_attribute_prefix) } | ||
super(attributes.except(*dynamic_attributes.keys)) | ||
set_dynamic_attributes(dynamic_attributes) | ||
end | ||
|
||
# Overrides update_attributes to take dynamic attributes into account | ||
def update_attributes(attributes) | ||
set_dynamic_attributes(attributes) | ||
super(attributes) | ||
end | ||
|
||
# Returns the dynamic attributes that will be persisted to the serialization column. This array can | ||
# be altered to force dynamic attributes to not be saved in the database or to persist other attributes, but | ||
# it is recommended to not change it at all. | ||
def persisting_dynamic_attributes | ||
@persisting_dynamic_attributes ||= [] | ||
end | ||
|
||
# Ensures the configured dynamic attribute field is serialized by AR. | ||
def self.included object | ||
object.serialize object.dynamic_attribute_field | ||
end | ||
|
||
private | ||
|
||
# Method that is called when a dynamic attribute is added to this model. It adds this attribute to the list | ||
# of attributes that will be persisited, creates an accessor and sets the attribute value. To reflect that the | ||
# attribute has been added, the serialization attribute will also be updated. | ||
def set_dynamic_attribute(att, value) | ||
att = att.to_s | ||
persisting_dynamic_attributes << att | ||
singleton_class.send(:attr_accessor, att) | ||
send(att + '=', value) | ||
update_dynamic_attribute(att, value) | ||
end | ||
|
||
# Called on object initialization or when calling update_attributes to convert passed dynamic attributes | ||
# into attributes that will be persisted by calling set_dynamic_attribute if it does not exist already. | ||
# The serialization column will also be updated and the detected dynamic attributes are removed from the passed | ||
# attributes hash. | ||
def set_dynamic_attributes(attributes) | ||
return if attributes.nil? | ||
|
||
attributes.each do |att, value| | ||
if att.to_s.starts_with?(self.dynamic_attribute_prefix) | ||
attributes.delete(att) | ||
unless respond_to?(att.to_s + '=') | ||
set_dynamic_attribute(att, value) | ||
else | ||
send(att.to_s + '=', value); | ||
update_dynamic_attribute(att, value) | ||
end | ||
end | ||
end | ||
end | ||
|
||
# Updates the serialization column with a new attribute and value. | ||
def update_dynamic_attribute(attribute, value) | ||
write_attribute(self.dynamic_attribute_field.to_s, (read_attribute(self.dynamic_attribute_field.to_s) || {}).merge(attribute.to_s => value)) | ||
end | ||
|
||
end |
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,6 @@ | ||
sqlite: | ||
adapter: sqlite | ||
database: ":memory:" | ||
sqlite3: | ||
adapter: sqlite3 | ||
database: ":memory:" |
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 |
---|---|---|
@@ -1,10 +1,39 @@ | ||
require 'rubygems' | ||
require 'test/unit' | ||
require 'shoulda' | ||
|
||
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) | ||
$LOAD_PATH.unshift(File.dirname(__FILE__)) | ||
|
||
gem "activerecord" | ||
require 'active_record' | ||
require 'dynamic_attributes' | ||
require 'pp' | ||
|
||
class Test::Unit::TestCase | ||
class DynamicModel < ActiveRecord::Base | ||
has_dynamic_attributes :dynamic_attribute_field => :dynamic_attributes, :dynamic_attribute_prefix => 'field_', :destroy_dynamic_attribute_for_nil => false | ||
end | ||
|
||
def load_schema | ||
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) | ||
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log") | ||
db_adapter = ENV['DB'] | ||
# no db passed, try one of these fine config-free DBs before bombing. | ||
db_adapter ||= begin | ||
require 'rubygems' | ||
require 'sqlite' | ||
'sqlite' | ||
rescue MissingSourceFile | ||
begin | ||
require 'sqlite3' | ||
'sqlite3' | ||
rescue MissingSourceFile | ||
end | ||
end | ||
|
||
if db_adapter.nil? | ||
raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3." | ||
end | ||
ActiveRecord::Base.establish_connection(config[db_adapter]) | ||
load(File.dirname(__FILE__) + "/schema.rb") | ||
require File.dirname(__FILE__) + '/../init.rb' | ||
end |
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,7 @@ | ||
ActiveRecord::Schema.define(:version => 0) do | ||
create_table :dynamic_models, :force => true do |t| | ||
t.string :title | ||
t.text :dynamic_attributes | ||
t.text :extra | ||
end | ||
end |
Oops, something went wrong.