Permalink
Browse files

Added AR methods especially #attributes

  • Loading branch information...
1 parent 88048c0 commit ba6a79042c13e5bfc769df26ca407659858e7ef5 @maiha committed Mar 16, 2010
View
49 README.markdown
@@ -20,16 +20,55 @@ This library provides some of them to Ohm::Model.
require 'ohm-arfreaks'
- class Page < Ohm::Model
+ class Video < Ohm::Model
attribute :url
+ set :tags
end
- Page.first
- # => #<Page:1 url="http://...">
+ Video.first
+ => #<Video id: 470, url: "a", tags: []>
- Page.create!(:url=>...)
+ Video.create!(:url=>...)
+ Video.new.save!
- Page.new.save!
+### Available Methods
+
+ def self.primary_key # AR
+ def self.columns # AR
+ def self.column_names # AR
+ def self.content_columns # AR
+ def self.columns_hash # AR
+ def self.create!(attributes) # AR
+ def save! # AR
+ def self.first(*args) # AR
+ def self.last(*args) # AR
+ def self.count(*args) # AR
+ def self.delete_all(cond = nil) # AR
+ def new_record? # AR
+ def attributes # AR
+
+ (reported by: % grep 'def ' lib/ohm-arfreaks.rb |grep '# AR' )
+
+### NOTE
+
+ Ohm::Model#attributes returns an names of attributed fields in vanilla ohm gem.
+ Of course, it's not familier to our ar-freaks, so it's overwritten by this gem.
+
+ class Video < Ohm::Model
+ attribute :url
+ set :tags
+ end
+
+ Video.first.attributes
+ => [:url] # in vanilla ohm
+ => {:url=>"a", :tags=>[]} # in ohm-arfreaks
+
+
+Test
+----
+
+ % redis-server spec/redis.conf # running on port:6380 in default
+ % spec -c spec
Homepage
View
2 Rakefile
@@ -6,7 +6,7 @@ AUTHOR = "maiha"
EMAIL = "maiha@wota.jp"
HOMEPAGE = "http://github.com/maiha/ohm-arfreaks"
SUMMARY = "Ohm::Model extensions for AR freaks"
-GEM_VERSION = "0.0.1"
+GEM_VERSION = "0.1.0"
spec = Gem::Specification.new do |s|
s.rubyforge_project = 'asakusarb'
View
115 lib/ohm-arfreaks.rb
@@ -1,34 +1,133 @@
module Ohm
class Model
######################################################################
+ ### Columns
+
+ Column = Struct.new(:name, :type)
+
+ def self.primary_key # AR
+ :id
+ end
+
+ def self.columns # AR
+ [Column.new(primary_key, :attribute)] +
+ content_columns +
+ [:collection, :counter].map do |type|
+ __send__("#{type}s").map{|name| Column.new(name, type)}
+ end.flatten
+ end
+
+ def self.column_names # AR
+ columns.map(&:name)
+ end
+
+ def self.content_columns # AR
+ attributes.map{|name| Column.new(name, :attribute)}
+ end
+
+ def self.columns_hash # AR
+ columns.inject({}){|h,c| h[c.name] = c; h}
+ end
+
+ ######################################################################
### Validations
RecordNotSaved = Class.new(RuntimeError)
- def self.create!(attributes)
+ def self.create!(attributes) # AR
object = new(attributes)
object.save!
object
end
- def save!
+ def save! # AR
save || raise(RecordNotSaved)
end
######################################################################
### Accessor methods
- def self.first(*args)
- (args.empty? ? all : find(*args)).first
+ def self.first(*args) # AR
+ (args.empty? ? all : find(*args))[0]
end
- def self.last(*args)
- (args.empty? ? all : find(*args)).last
+ def self.last(*args) # AR
+ (args.empty? ? all : find(*args))[-1]
end
- # TODO: should use native 'count' method for performance reason
- def self.count(*args)
+ def self.count(*args) # AR
(args.empty? ? all : find(*args)).size
end
+
+ ######################################################################
+ ### ActiveRecord class methods
+
+ def self.delete_all(cond = nil) # AR
+ if cond
+ raise NotImplementedError, "Sorry, conditional delete_all is not implemented yet"
+ else
+ all.each(&:delete)
+ end
+ end
+
+ ######################################################################
+ ### ActiveRecord instance methods
+
+ def new_record? # AR
+ new?
+ end
+
+ ######################################################################
+ ### Overwrite attributes (and related methods)
+
+ def attributes # AR
+ hash = {}
+ self.class.attributes.each do |attr|
+ hash[attr] = __send__(attr)
+ end
+ self.class.collections.each do |attr|
+ hash[attr] = (begin __send__(attr).to_a; rescue MissingID; []; end)
+ end
+ self.class.counters.each do |attr|
+ hash[attr] = __send__(attr).to_i
+ end
+ return hash
+ end
+
+ def delete
+ delete_from_indices
+ delete_attributes(self.class.attributes)
+ delete_attributes(self.class.counters)
+ delete_attributes(self.class.collections)
+ delete_model_membership
+ self
+ end
+
+ def inspect
+ attrs = attributes.map{|(k,v)| "#{k}: #{v.inspect}"}.join(', ')
+ "#<#{self.class} id: #{new_record? ? "nil" : id}, #{attrs}>"
+ end
+
+ # Write attributes using SET
+ # This method will be removed once MSET becomes standard.
+ def write_with_set
+ self.class.attributes.each do |att|
+ value = send(att)
+ value.to_s.empty? ?
+ db.set(key(att), value) :
+ db.del(key(att))
+ end
+ end
+
+ # Write attributes using MSET
+ # This is the preferred method, and will be the only option
+ # available once MSET becomes standard.
+ def write_with_mset
+ unless self.class.attributes.empty?
+ rems, adds = self.class.attributes.map { |a| [key(a), send(a)] }.partition { |t| t.last.to_s.empty? }
+ db.del(*rems.flatten.compact) unless rems.empty?
+ db.mset(adds.flatten) unless adds.empty?
+ end
+ end
end
end
View
8 ohm-arfreaks.gemspec
@@ -2,19 +2,19 @@
Gem::Specification.new do |s|
s.name = %q{ohm-arfreaks}
- s.version = "0.0.1"
+ s.version = "0.1.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["maiha"]
- s.date = %q{2010-02-05}
+ s.date = %q{2010-03-17}
s.description = %q{Ohm::Model extensions for AR freaks}
s.email = %q{maiha@wota.jp}
s.extra_rdoc_files = ["README.markdown", "LICENSE"]
- s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/ohm-arfreaks.rb"]
+ s.files = ["LICENSE", "README.markdown", "Rakefile", "lib/ohm-arfreaks.rb", "spec/db", "spec/db/redis.pid", "spec/attributes_spec.rb", "spec/redis.conf", "spec/provide_helper.rb", "spec/columns_spec.rb", "spec/model.rb", "spec/methods_spec.rb", "spec/its_helper.rb", "spec/spec_helper.rb", "spec/validations_spec.rb"]
s.homepage = %q{http://github.com/maiha/ohm-arfreaks}
s.require_paths = ["lib"]
s.rubyforge_project = %q{asakusarb}
- s.rubygems_version = %q{1.3.5}
+ s.rubygems_version = %q{1.3.6}
s.summary = %q{Ohm::Model extensions for AR freaks}
if s.respond_to? :specification_version then
View
54 spec/attributes_spec.rb
@@ -0,0 +1,54 @@
+require File.join(File.dirname(__FILE__), '/spec_helper')
+
+describe Ohm::Model do
+ provide :attributes do
+ subject {
+ Video.create!(:url => "http://localhost/")
+ }
+
+ ######################################################################
+ ### Attribute
+
+ its(:attributes) { should be_kind_of(Hash) }
+
+ it "should contain given attribute" do
+ subject.attributes[:url].should == "http://localhost/"
+ end
+
+ ######################################################################
+ ### Counter
+
+ it "should contain given counter" do
+ subject.attributes[:viewed].should == 0
+ end
+
+ it "should contain a valid counter value after incr and reload" do
+ subject.incr(:viewed)
+ subject.attributes[:viewed].should == 1
+ Video[subject.id].attributes[:viewed].should == 1
+ end
+
+ ######################################################################
+ ### Set
+
+ it "should contain given set as array" do
+ subject.tags.concat "foo"
+ subject.attributes[:tags].should be_kind_of(Array)
+ end
+
+ it "should contain given set" do
+ subject.tags.concat "foo"
+ subject.attributes[:tags].should == ["foo"]
+ end
+
+ it "should contain given set as array" do
+ subject.tags.concat "foo"
+ subject.tags.concat "bar"
+ subject.attributes[:tags].should == ["foo", "bar"]
+ end
+
+ it "should contain empty array when no set given" do
+ subject.attributes[:tags].should == []
+ end
+ end
+end
View
75 spec/columns_spec.rb
@@ -0,0 +1,75 @@
+require File.join(File.dirname(__FILE__), '/spec_helper')
+
+describe Ohm::Model do
+ ######################################################################
+ ### columns
+
+ it do
+ Video.should provide(:columns)
+ end
+
+ describe ".columns" do
+ it "should return an array of Column" do
+ Video.columns.map(&:class).should == [Ohm::Model::Column, Ohm::Model::Column, Ohm::Model::Column, Ohm::Model::Column]
+ end
+
+ it "should contains column names" do
+ Video.columns.map(&:name).should == [:id, :url, :tags, :viewed]
+ end
+
+ it "should contains column types" do
+ Video.columns.map(&:type).should == [:attribute, :attribute, :collection, :counter]
+ end
+ end
+
+ ######################################################################
+ ### column_names
+
+ it do
+ Video.should provide(:column_names)
+ end
+
+ describe ".column_names" do
+ it "should return an array of Symbol" do
+ Video.column_names.map(&:class).uniq.should == [Symbol]
+ end
+
+ it "should return column names" do
+ Video.column_names.should == [:id, :url, :tags, :viewed]
+ end
+ end
+
+ ######################################################################
+ ### content_columns
+
+ it do
+ Video.should provide(:content_columns)
+ end
+
+ describe ".content_columns" do
+ it "should return an array of Column" do
+ Video.content_columns.map(&:class).uniq.should == [Ohm::Model::Column]
+ end
+
+ it "should contains only attribute columns" do
+ Video.content_columns.map(&:type).uniq.should == [:attribute]
+ end
+
+ it "should not contains primary keys" do
+ Video.content_columns.map(&:name).should_not include(:id)
+ end
+ end
+
+ ######################################################################
+ ### columns_hash
+
+ it do
+ Video.should provide(:columns_hash)
+ end
+
+ describe ".columns_hash" do
+ it "should return a hash" do
+ Video.columns_hash.should be_kind_of(Hash)
+ end
+ end
+end
View
0 spec/db/redis.pid
No changes.
View
15 spec/its_helper.rb
@@ -0,0 +1,15 @@
+module Spec
+ module Example
+ module Subject
+ module ExampleGroupMethods
+ def its(*args, &block)
+ describe(args.first) do
+ define_method(:subject) { super().send(*args) }
+ it(&block)
+ end
+ end
+ end
+ end
+ end
+end
+
View
86 spec/methods_spec.rb
@@ -0,0 +1,86 @@
+require File.join(File.dirname(__FILE__), '/spec_helper')
+
+describe Ohm::Model do
+ ######################################################################
+ ### delete_all
+
+ it do
+ Ohm::Model.should provide(:delete_all)
+ end
+
+ describe ".delete_all" do
+ before do
+ Video.create!(:url=>"a")
+ @count = Video.count
+ end
+
+ it "should delete all records" do
+ lambda { Video.delete_all }.should change(Video, :count).from(@count).to(0)
+ end
+
+ it "should raise NotImplementedError when args are given" do
+ lambda { Video.delete_all(:some_cond) }.should raise_error(NotImplementedError)
+ end
+ end
+
+ context "(url 'a' and 'b')" do
+ before do
+ Video.delete_all
+ Video.create!(:url=>"a")
+ Video.create!(:url=>"b")
+ end
+
+ ######################################################################
+ ### first
+ it do
+ Ohm::Model.should provide(:first)
+ end
+
+ describe ".first" do
+ it "should return an record" do
+ # TODO: how to know the orders
+ Video.first.url.should match(/\Aa|b\Z/)
+ end
+ end
+
+ ######################################################################
+ ### all
+
+ it do
+ Ohm::Model.should provide(:all)
+ end
+
+ describe ".all" do
+ it "should return all records" do
+ Video.all.map(&:url).sort.should == ["a", "b"]
+ end
+ end
+
+ ######################################################################
+ ### last
+
+ it do
+ Ohm::Model.should provide(:last)
+ end
+
+ describe ".last" do
+ it "should return an record" do
+ # TODO: how to know the orders
+ Video.last.url.should match(/\Aa|b\Z/)
+ end
+ end
+ end
+
+ ######################################################################
+ ### new_recoard?
+
+ provide :new_record? do
+ it "should return true for non saved record" do
+ Video.new.new_record?.should == true
+ end
+
+ it "should return false for existing record" do
+ Video.create!(:url=>"a").new_record?.should == false
+ end
+ end
+end
View
6 spec/model.rb
@@ -0,0 +1,6 @@
+
+class Video < Ohm::Model
+ attribute :url
+ counter :viewed
+ set :tags
+end
View
45 spec/provide_helper.rb
@@ -0,0 +1,45 @@
+######################################################################
+### provide matcher
+Spec::Matchers.define :provide do |expected|
+ match do |obj|
+ (obj.public_methods + obj.protected_methods + obj.private_methods).include?(expected.to_s)
+ end
+end
+
+module Spec
+ module Example
+ module Subject
+ module ExampleGroupMethods
+ # == Examples
+ #
+ # describe User do
+ # subject { User.new }
+ # provide :name
+ #
+ # [intead of]
+ #
+ # it "should provide #name" do
+ # methods = subject.public_methods + subject.protected_methods + subject.private_methods
+ # methods.should include("name")
+ # end
+ # end
+ #
+ def provide(name, &block)
+ it "should provide ##{name}" do
+ subject.should provide(name)
+ end
+
+ if block
+ describe("##{name}") do
+ define_method(name) do |*args|
+ subject.send(name, *args)
+ end
+ class_eval(&block)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
View
13 spec/redis.conf
@@ -0,0 +1,13 @@
+dir ./spec/db
+pidfile ./spec/db/redis.pid
+daemonize yes
+port 6380
+timeout 300
+
+save 900 10000
+
+loglevel warning
+
+logfile stdout
+
+databases 16
View
22 spec/spec_helper.rb
@@ -0,0 +1,22 @@
+
+require 'spec'
+require 'rr'
+
+Spec::Runner.configure do |config|
+ config.mock_with RR::Adapters::Rspec
+end
+
+require 'ohm'
+Ohm.connect(:port=>6380) # this port number should be defined in redis.conf
+Dir.glob(File.join(File.dirname(__FILE__), '/../lib/*.rb')).each{|lib| require lib}
+require File.join(File.dirname(__FILE__), '/its_helper')
+require File.join(File.dirname(__FILE__), '/provide_helper')
+require File.join(File.dirname(__FILE__), '/model')
+
+def path(key)
+ Pathname(File.join(File.dirname(__FILE__) + "/fixtures/#{key}"))
+end
+
+def data(key)
+ (@__fixture_data_cache__ ||= {})[key] ||= path(key).read{}
+end
View
9 spec/validations_spec.rb
@@ -0,0 +1,9 @@
+require File.join(File.dirname(__FILE__), '/spec_helper')
+
+describe Video do
+ it do
+ Video.should provide(:create!)
+ end
+
+ provide :save!
+end

0 comments on commit ba6a790

Please sign in to comment.