text_record 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ coverage/
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'spec/rake/spectask'
4
+
5
+ desc "Run all specs"
6
+ Spec::Rake::SpecTask.new('spec') do |t|
7
+ t.spec_opts = ['--colour --format specdoc --loadby mtime --reverse']
8
+ t.spec_files = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+ desc "Print specdocs"
12
+ Spec::Rake::SpecTask.new(:doc) do |t|
13
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
14
+ t.spec_files = FileList['spec/*_spec.rb']
15
+ end
16
+
17
+ desc "Generate RCov code coverage report"
18
+ Spec::Rake::SpecTask.new('rcov') do |t|
19
+ t.spec_files = FileList['spec/*_spec.rb']
20
+ t.rcov = true
21
+ t.rcov_opts = ['--exclude', '/gems/']
22
+ end
23
+
24
+ task :default => :spec
25
+
26
+ begin
27
+ require 'jeweler'
28
+ Jeweler::Tasks.new do |gemspec|
29
+ gemspec.name = "text_record"
30
+ gemspec.summary = "Models through text files"
31
+ gemspec.description = "Write your models in text files through YAML/Makdown/TextFile whatever you please. Take the forms out of your code and harness the power of your favorite editor."
32
+ gemspec.email = "Adman1965@gmaiil.com"
33
+ gemspec.homepage = "http://github.com/Adman65/text_record"
34
+ gemspec.authors = ["Adam Hawkins"]
35
+ end
36
+
37
+ Jeweler::GemcutterTasks.new
38
+ rescue LoadError
39
+ puts "Jeweler not available. Install it with: gem install jeweler"
40
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,27 @@
1
+ module TextRecord
2
+ class Attribute
3
+ attr_reader :name, :options
4
+
5
+ def self.for_options(options)
6
+ (options[:from].eql?(:yml)) ? YAMLAttribute : FileAttribute
7
+ end
8
+
9
+ def initialize(name, options)
10
+ @name = name
11
+ @options = options
12
+ end
13
+ end
14
+
15
+ class YAMLAttribute < Attribute
16
+ def parse(obj)
17
+ obj.instance_variable_set("@#{name.to_s.underscore}".to_sym, obj.yml[name.to_s.camelcase])
18
+ end
19
+ end
20
+
21
+ class FileAttribute < Attribute
22
+ def parse(obj)
23
+ file_contents = IO.read("#{obj.directory}/#{options[:from]}")
24
+ obj.instance_variable_set("@#{name.to_s.underscore}".to_sym, file_contents)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,65 @@
1
+ require 'yaml'
2
+ require 'forwardable'
3
+
4
+ module TextRecord
5
+ class Base
6
+ extend Forwardable
7
+
8
+ attr_reader :slug
9
+
10
+ def_delegators :configuration, :content_path, :attribute_file
11
+
12
+ # Since attributes are unique to each subclass, have to the object's metaclass since it will be
13
+ # unique to each subclass
14
+
15
+ class << self
16
+ attr_reader :attributes #aka @attributes in this block
17
+
18
+ def attribute(name, options = {})
19
+ options.assert_valid_keys :from
20
+
21
+ @attributes ||= []
22
+ options[:from] ||= :yml
23
+
24
+ self.class_eval do
25
+ attr_reader name.to_s.underscore.to_sym
26
+ end
27
+
28
+ @attributes << Attribute.for_options(options).new(name, options)
29
+ end
30
+ end
31
+
32
+ # class methods
33
+ def self.directory
34
+ File.join(TextRecord.configuration.content_path, self.name.pluralize.underscore)
35
+ end
36
+
37
+ # Instance Methods
38
+ def initialize(path)
39
+ @slug = path
40
+
41
+ raise SlugNotFoundError.new("#{directory} could not be found.") unless File.directory?(directory)
42
+
43
+ self.class.attributes.each do |attribute| # class attributes, attr_readers to be added to object
44
+ attribute.parse(self)
45
+ end
46
+ end
47
+
48
+ def configuration
49
+ TextRecord.configuration
50
+ end
51
+
52
+ def directory
53
+ name = self.class.name.underscore.pluralize
54
+ File.join(content_path, name, slug)
55
+ end
56
+
57
+ def yml_file
58
+ File.join(directory, attribute_file)
59
+ end
60
+
61
+ def yml
62
+ @yml ||= YAML.load_file(yml_file)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,21 @@
1
+ module TextRecord
2
+ def self.configuration
3
+ @configuration ||= Configuration.new
4
+ end
5
+
6
+
7
+ def self.configure(config=configuration)
8
+ yield config if block_given?
9
+ @configuration = config
10
+ end
11
+
12
+
13
+ class Configuration
14
+ attr_accessor :content_path
15
+ attr_accessor :attribute_file
16
+
17
+ def initialize
18
+ self.attribute_file = "attributes.yml"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module TextRecord
2
+ # General error class
3
+ class TextRecordError < StandardError
4
+ end
5
+
6
+ # Raised when the attribute file cannot be found
7
+ class AttributeFileNotFoundError < TextRecordError
8
+ end
9
+
10
+ # Raised when slug cannot be found
11
+ class SlugNotFoundError < TextRecordError
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'active_support'
4
+ rescue
5
+ require 'active_support'
6
+ end
7
+
8
+ module TextRecord
9
+ end
10
+
11
+ require 'text_record/configuration'
12
+ require 'text_record/errors'
13
+ require 'text_record/attribute'
14
+ require 'text_record/base'
data/readme.markdown ADDED
@@ -0,0 +1,76 @@
1
+ # TextRecord
2
+
3
+ I needed to create a personal website with a lot of hand written content like blog posts, book reviews, and project documentations. Being a lazy programmer, I didn't want to have to write useless admin interfaces for creating all this stuff. Also, you can't get the full editing experience inside a textarea. TextMate totally owns textareas. So I needed an easy way to create massive amounts of text and organize them. Essentially, I wanted a way to treat my website as an open word document that I could save and push to heroku.
4
+
5
+ After playing around with the idea and implementation I came up with TextRecord. TextRecord uses patterns from ActiveRecord to model objects in various text files.
6
+
7
+ # Example
8
+
9
+ ## Install TextRecord
10
+
11
+ sudo gem install text_record # not gemmed yet
12
+
13
+ ## Creating a Model
14
+
15
+ All model files live in the content directory. Then each model instance has its own folder. The folder name is the model's slug. Inside each slug directory are the files that contain the model's attributes. Here is an exmaple folder structure
16
+
17
+ /content
18
+ /blog_posts
19
+ /TextRecordTest
20
+ attributes.yml
21
+ post.markdown
22
+
23
+ Folder names under content should be underscored. Your slug should be something simple either in camelcase or dashed. Think of the slug has an id column in table.
24
+
25
+ Most of the time your attribute will come from the attributes.yml. Defining attributes in the yml file is easy. Here is an example attributes.yml:
26
+
27
+ Title: Introducting TextRecord
28
+
29
+ Introduction: This is a paragraph explaining TextRecord
30
+
31
+ Tags: [Ruby, Gems, TextRecord]
32
+
33
+ As you can see this is just vanilla YAML. You can use any YAML sturcture: list, hashes, blocks etc. They are translated into their Ruby equivalents using Ruby's YAML libary. Next we define what attributes to load from the file. By defining the attributes to load instead of autoloading everything in file, you can keep some things in 'development' mode. For example, you're working on creating summaries for long posts, but aren't ready to use them in production yet. Now, lets create the Blog Post model.
34
+
35
+ # blog_post.rb
36
+ BlogPost < TextRecord::Base
37
+ attribute :title
38
+ attribute :introduction
39
+ attribute :tags
40
+ end
41
+
42
+ Use the attribute class method to define methods. Attribute names should be underscored. Attribute names in the yaml file should be camel cased. BlogPost correspond to blog_posts under /content. Finally configure TextRecord.
43
+
44
+ TextRecord.configure do |config|
45
+ config.content_path = "/path/to/content"
46
+ end
47
+
48
+ Now you can instantiate your models:
49
+
50
+ blog_post = BlogPost.new 'TextRecordText' # argument is a slug
51
+ #<BlogPost:0x101e1ee00 @tags=["Ruby", "Gems", "TextRecord"], @slug="TextRecord", @introduction="This is a paragraph explaining TextRecord", @title="Introducting TextRecord", @yml={"Title"=>"Introducting TextRecord", "Tags"=>["Ruby", "Gems", "TextRecord"], "Introduction"=>"This is a paragraph explaining TextRecord"}>
52
+ >> blog_post.title
53
+ => "Introducting TextRecord"
54
+ >> blog_post.slug
55
+ => "TextRecord"
56
+ >> blog_post.tags
57
+ => ["Ruby", "Gems", "TextRecord"]
58
+
59
+ # Using Other Files
60
+
61
+ You can also pull in attributes from other files. For example, storing markdown/textile or formats that require spacing/tabs in yaml files is difficult. The attribute takes on option: :from. Use from to tell TextRecord to load an attribute from another file. For exmaple, the post text is stored in markdown as 'post' from post.markdown. post.markdown is located under a slug directory.
62
+
63
+ class BlogPost < TextRecord::Base
64
+ attribute :post, :from => 'post.markdown'
65
+ end
66
+
67
+ That will load the contents for post.markdown into the post attr_reader
68
+
69
+
70
+ # Limitations
71
+
72
+ TextRecord models are **READ ONLY**. All attributes are attr_readers. Why? Because TextRecord is only a way to encapsulate your text files into objects that you can use in your program. If you want to created/edit/delete models, update your slugs/text files.
73
+
74
+ # Working On
75
+ * **ActiveRecord style finders**
76
+
@@ -0,0 +1,5 @@
1
+ Title: Text Record
2
+
3
+ IntroductionText: This is an introduction paragraph
4
+
5
+ Fixnum: 5
@@ -0,0 +1 @@
1
+ This is from the markdown file
@@ -0,0 +1,5 @@
1
+ class BlogPost < TextRecord::Base
2
+ attribute :title
3
+ attribute :introduction_text
4
+ attribute :text, :from => 'post.markdown'
5
+ end
data/spec/base_spec.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe TextRecord::Base do
4
+ describe "#attribute" do
5
+
6
+ it "should raise an error if the keys are invalid" do
7
+ lambda {TextRecord::Base.attribute(:name, :adfsadf => "asdfasdfasdf")}.should raise_error
8
+ end
9
+
10
+ describe TextRecord::YAMLAttribute do
11
+ before(:each) do
12
+ class AttributeTest < TextRecord::Base
13
+ attribute :name
14
+ end
15
+ end
16
+
17
+ after(:each) do
18
+ AttributeTest.reset_attributes!
19
+ end
20
+
21
+ it "should create a new YAML attribute by default" do
22
+ AttributeTest.attributes.first.should be_a(TextRecord::YAMLAttribute)
23
+ end
24
+
25
+ it "should set the @name of a new YAML attribute" do
26
+ AttributeTest.attributes.first.name.should eql(:name)
27
+ end
28
+ end
29
+
30
+ describe TextRecord::FileAttribute do
31
+ before(:each) do
32
+ class AttributeTest < TextRecord::Base
33
+ attribute :post, :from => 'post.md'
34
+ end
35
+ end
36
+
37
+ after(:each) do
38
+ AttributeTest.reset_attributes!
39
+ end
40
+
41
+ it "should should create a new FILEAttribute when given a :from key" do
42
+ AttributeTest.attributes.first.should be_a(TextRecord::FileAttribute)
43
+ end
44
+
45
+ it "should set the @name of a new File attribute" do
46
+ AttributeTest.attributes.first.name.should eql(:post)
47
+ end
48
+
49
+ it "should set the @options for a new File attribute" do
50
+ AttributeTest.attributes.first.options.should eql({:from => 'post.md'})
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,84 @@
1
+ # this spec essentially tests TextRecord::Base since TextRecord::Base is essentially
2
+ # an abstract class.
3
+
4
+ require 'spec_helper'
5
+
6
+ describe BlogPost do
7
+ describe "#directory" do
8
+ it "should return the TextRecord.configuration's content_path joined with the class name" do
9
+ BlogPost.directory.should eql(File.join(TextRecord.configuration.content_path, "blog_posts"))
10
+ end
11
+ end
12
+
13
+ describe "#initialize" do
14
+ it "should raise an error given an incorrect slug" do
15
+ # tests for TextRecord::SlugNotFound that has the slug in the error description somewhere
16
+ lambda { BlogPost.new("fakeslug") }.should raise_error(TextRecord::SlugNotFoundError, /fakeslug/)
17
+ end
18
+
19
+ it "should set the slug" do
20
+ BlogPost.new("TextRecord").slug.should eql("TextRecord")
21
+ end
22
+ end
23
+ end
24
+
25
+ describe "A Blog Post" do
26
+ before(:each) do
27
+ @blog_post = BlogPost.new('TextRecord')
28
+ end
29
+
30
+ describe "attributes from yaml" do
31
+ it "should have a title attribute" do
32
+ @blog_post.respond_to?(:title).should be_true
33
+ end
34
+
35
+ it "should make the title a string" do
36
+ @blog_post.title.should be_a(String)
37
+ end
38
+
39
+ it "should take the title from the file" do
40
+ @blog_post.title.should eql('Text Record')
41
+ end
42
+
43
+ it "should not have title writer" do
44
+ @blog_post.respond_to?(:title=).should be_false
45
+ end
46
+
47
+ it "should have a slug" do
48
+ @blog_post.slug.should eql('TextRecord')
49
+ end
50
+
51
+ it "should have an introduction_text attribute" do
52
+ @blog_post.respond_to?(:introduction_text).should be_true
53
+ end
54
+
55
+ it "should set the introduction_text from the yaml file" do
56
+ @blog_post.introduction_text.should eql('This is an introduction paragraph')
57
+ end
58
+
59
+ it "should not have an attribute writer for introduction_text" do
60
+ @blog_post.respond_to?(:introduction_text=).should be_false
61
+ end
62
+ end
63
+
64
+ describe "attributes from files" do
65
+ it "should have a text attribute" do
66
+ @blog_post.respond_to?(:text).should be_true
67
+ end
68
+
69
+ it "should assign text to the whole file contents" do
70
+ @blog_post.text.should eql('This is from the markdown file')
71
+ end
72
+
73
+ it "should not have an text attribute writer" do
74
+ @blog_post.respond_to?(:text=).should be_false
75
+ end
76
+ end
77
+
78
+
79
+ describe "#directory" do
80
+ it "should return the content path plus the slug" do
81
+ BlogPost.new('TextRecord').directory.should eql(File.join(TextRecord.configuration.content_path, "blog_posts","TextRecord"))
82
+ end
83
+ end
84
+ end
File without changes
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ LIB_PATH = File.expand_path("#{File.dirname(__FILE__)}/../lib")
5
+ $: << LIB_PATH
6
+
7
+ require 'text_record'
8
+
9
+ APP_PATH = File.expand_path("#{File.dirname(__FILE__)}/app")
10
+
11
+ TextRecord.configure do |config|
12
+ config.content_path = "#{APP_PATH}/content"
13
+ end
14
+
15
+ $: << "#{APP_PATH}/models"
16
+
17
+ Dir["#{APP_PATH}/models/*.rb"].each do |model|
18
+ require model
19
+ end
20
+
21
+ Spec::Runner.configure do |config|
22
+ config.after(:each) do
23
+ # nothing as of now
24
+ end
25
+ end
26
+
27
+ # Little monkey patch to reset attributes during testing
28
+ module TextRecord
29
+ class Base
30
+ def self.reset_attributes!
31
+ @attributes = []
32
+ end
33
+ end
34
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: text_record
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Adam Hawkins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-04 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Write your models in text files through YAML/Makdown/TextFile whatever you please. Take the forms out of your code and harness the power of your favorite editor.
17
+ email: Adman1965@gmaiil.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - .gitignore
26
+ - Rakefile
27
+ - VERSION
28
+ - lib/text_record.rb
29
+ - lib/text_record/attribute.rb
30
+ - lib/text_record/base.rb
31
+ - lib/text_record/configuration.rb
32
+ - lib/text_record/errors.rb
33
+ - readme.markdown
34
+ - spec/app/content/blog_posts/TextRecord/attributes.yml
35
+ - spec/app/content/blog_posts/TextRecord/post.markdown
36
+ - spec/app/models/blog_post.rb
37
+ - spec/base_spec.rb
38
+ - spec/blog_post_spec.rb
39
+ - spec/configuration_spec.rb
40
+ - spec/spec_helper.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/Adman65/text_record
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --charset=UTF-8
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.3.5
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Models through text files
69
+ test_files:
70
+ - spec/app/models/blog_post.rb
71
+ - spec/base_spec.rb
72
+ - spec/blog_post_spec.rb
73
+ - spec/configuration_spec.rb
74
+ - spec/spec_helper.rb