Skip to content

Commit

Permalink
Added shortcuts for getters/setters of table less model's attributes,…
Browse files Browse the repository at this point in the history
… in the parent model
  • Loading branch information
Vito Botta committed Jul 24, 2011
1 parent b8943c7 commit 32b438c
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 1 deletion.
49 changes: 49 additions & 0 deletions README.md
Expand Up @@ -161,6 +161,55 @@ Of course, if a value is specified for an attribute when creating an instance of
=> <#SeoOptions created_at=Sun Jul 24 18:47:00 +0100 2011>
```

For each of the attribute defined in the tableless model, shortcuts for both setter and getter are automatically defined in the parent model, unless the parent model already has a method of its own by the same name.

So, for instance, if you have the tableless model:

``` ruby
class SeoOptions < ActiveRecord::TablelessModel

attribute :title_tag, :type => :string, :default => "default title tag"
attribute :meta_description, :type => :string, :default => ""
attribute :meta_keywords, :type => :string, :default => ""
attribute :noindex, :type => :boolean, :default => false
attribute :nofollow, :type => :boolean, :default => false
attribute :noarchive, :type => :boolean, :default => false

end
```

which is used by a parent model:

``` ruby
class Page < ActiveRecord::Base

has_tableless :seo => SeoOptions

end
```

you can get/set attributes of the tableless model directly from the parent model:


``` ruby
# this...
>> page.title_tag
=> "default title tag"

# is same as...
>> page.seo_options.title_tag
=> "default title tag"
```

For boolean attributes (or also truthy/falsy ones) you can also make calls to special getters ending with "?", so to get true or false in return, depending on the actual value of the attribute:

``` ruby
# this...
>> page.title_tag?
=> true
```


## Validations

Tableless Model uses the Validatable gem to support validations methods and callbacks (such as "after_validation").
Expand Down
7 changes: 7 additions & 0 deletions lib/activerecord/base/class_methods.rb
@@ -1,6 +1,9 @@
module Base
module ClassMethods

attr_reader :tableless_models


#
#
# Macro to attach a tableless model to a parent, table-based model.
Expand All @@ -24,6 +27,10 @@ module ClassMethods
#
def has_tableless(column)
column_name = column.class == Hash ? column.collect{|k,v| k}.first.to_sym : column

@tableless_models ||= []
@tableless_models << column_name


# if only the column name is given, the tableless model's class is expected to have that name, classified, as class name
class_type = column.class == Hash ? column.collect{|k,v| v}.last : column.to_s.classify.constantize
Expand Down
15 changes: 15 additions & 0 deletions lib/activerecord/base/instance_methods.rb
@@ -0,0 +1,15 @@
class ActiveRecord::Base
#
#
# delegates method calls for unknown methods to the tableless model
#
def method_missing method, *args, &block
self.class.tableless_models.each do |column_name|
serialized_attribute = send(column_name)
return serialized_attribute.send(method, *args, &block) if serialized_attribute.respond_to?(method)
end

super method, *args, &block
end
end

1 change: 1 addition & 0 deletions lib/tableless_model.rb
Expand Up @@ -2,6 +2,7 @@

require "validatable"
require "activerecord/base/class_methods"
require "activerecord/base/instance_methods"
require "tableless_model/class_methods"
require "tableless_model/instance_methods"
require "tableless_model/version"
Expand Down
18 changes: 18 additions & 0 deletions lib/tableless_model/instance_methods.rb
Expand Up @@ -89,5 +89,23 @@ def merge(hash)
raise NoMethodError
end


#
# allows calls to attribute_name?, returning a true or false depending
# on whether the actual value of the attribute is truthy or falsy
#
def method_missing sym, *args, &block
attribute_name = sym.to_s.gsub(/^(.*)\?$/, "\\1")
if respond_to?(attribute_name)
!!send(attribute_name, *args, &block)
else
super sym, *args, &block
end
end

def respond_to?(method)
super || self.class.attributes.keys.include?(method) || self.class.attributes.keys.include?("#{method}=".to_sym) || super(method.to_s.gsub(/^(.*)\?$/, "\\1"))
end

end
end
31 changes: 30 additions & 1 deletion spec/tableless_model_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"

ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
ActiveRecord::Base.connection.execute(" create table test_models (options varchar(50)) ")
ActiveRecord::Base.connection.execute(" create table test_models (id integer, options varchar(50)) ")

class ModelOptions < ActiveRecord::TablelessModel
attribute :no_default_value_no_type_attribute
Expand Down Expand Up @@ -83,6 +83,35 @@ class TestModel < ActiveRecord::Base
end
end
end

context "when receives a message with the name of an attribute in the tableless model" do
before(:each) do
test_model.options = test_values
end

context "and it doesn't have a method of its own by that name" do
it "delegates the method call to the tableless model" do
test_values.each do |k, v|
test_model.send(k).should == v
end
end

it "delegates setters too" do
instance = TestModel.new

test_values.each do |k, v|
instance.send("#{k}=", v)
instance.options.send(k).should == v
end
end

it "can delegate calls to attribute_name?" do
test_values.each do |k, v|
test_model.send("#{k}?").should == !!v
end
end
end
end

describe "#options" do
it "is an instance of the tableless model" do
Expand Down

0 comments on commit 32b438c

Please sign in to comment.