flat 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b8dda342a45881d96ffeec6a23b57f9697d820a0
4
+ data.tar.gz: 2d4a6c80305e3cf117f145d45ed789671e1934c7
5
+ SHA512:
6
+ metadata.gz: 70a2839490d96e0dbcc8b32376c816b5f833bbd9935aa81b97bf79ed0ce7d3e21dbe916b7e2339de5c56bb14070af71a9da4b619707af96ddeabb62985794fea
7
+ data.tar.gz: faec279d9e961e3de45ad854527a4d4d5107c68720500b08c56d67c1364d0f12c4f7b4c9312df98b6bb12431a2903ad3848f363b97ba472205d4705c496626da
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ flat
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/.travis.yml ADDED
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ cache: bundler
3
+
4
+ rvm:
5
+ - 2.1.2
6
+
7
+ script: 'bundle exec rake'
8
+
9
+ notifications:
10
+ email:
11
+ recipients:
12
+ - mriffe@gmail.com
13
+ on_failure: change
14
+ on_success: never
15
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in flat.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Mel Riffe
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # Flat
2
+
3
+ [![Build Status](https://travis-ci.org/juicyparts/flat.svg?branch=develop)](https://travis-ci.org/juicyparts/flat)
4
+ [![Coverage Status](https://coveralls.io/repos/juicyparts/flat/badge.png?branch=develop)](https://coveralls.io/r/juicyparts/flat)
5
+
6
+ Flat is a library to make processing Flat Flies as easy as CSV files. Easily process flat files with Flat. Specify the format in a subclass of Flat::File and read and write until the cows come home.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'flat'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install flat
23
+
24
+ ## Usage
25
+
26
+ A simple example for a flat file class for grabbing information about
27
+ people might look like this:
28
+
29
+ # Actual plain text, flat file data, 29 bytes
30
+ #
31
+ # 10 20
32
+ # 012345678901234567890123456789
33
+ # Walt Whitman 18190531
34
+ # Linus Torvalds 19691228
35
+
36
+ class People < Flat::File
37
+
38
+ add_field :first_name, width: 10, filter: :trim
39
+ add_field :last_name, width: 10, filter: ->(v) { v.strip }
40
+ add_field :birthday, width: 8, filter: BirthdayFilter
41
+ pad :autoname, width: 2
42
+
43
+ def self.trim(v)
44
+ v.trim
45
+ end
46
+
47
+ end
48
+
49
+ p = People.new
50
+
51
+ p.each_record(open('somefile.dat')) do |person|
52
+
53
+ puts "First Name: #{person.first_name}"
54
+ puts "Last Name : #{person.last_name}"
55
+ puts "Birthday : #{person.birthday}"
56
+
57
+ puts person.to_s
58
+ end
59
+
60
+ Consult the [RDocs](http://rubydoc.info/github/juicyparts/flat) for additional examples, and information on Filters and
61
+ Formatters.
62
+
63
+ ## Contributing
64
+
65
+ 1. Fork it ( https://github.com/juicyparts/flat/fork )
66
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
67
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
68
+ 4. Push to the branch (`git push origin my-new-feature`)
69
+ 5. Create a new Pull Request
70
+
71
+ ## Inspiration
72
+
73
+ I had been looking for a library to handle flat files in a manner similar to
74
+ the CSV library and stumbled upon the following:
75
+
76
+ * flat_filer (http://rubygems.org/gems/flat_filer)
77
+ * flat_filer (https://github.com/xforty/flat_filer)
78
+ * flat_filer (https://github.com/cheapRoc/flat_filer)
79
+
80
+ They all appeared to be abandoned so I decided to resurrect them with my own
81
+ spin on the implementation.
82
+
83
+ # LICENSE
84
+
85
+ Copyright (c) 2014 Mel Riffe
86
+
87
+ MIT License
88
+
89
+ Permission is hereby granted, free of charge, to any person obtaining
90
+ a copy of this software and associated documentation files (the
91
+ "Software"), to deal in the Software without restriction, including
92
+ without limitation the rights to use, copy, modify, merge, publish,
93
+ distribute, sublicense, and/or sell copies of the Software, and to
94
+ permit persons to whom the Software is furnished to do so, subject to
95
+ the following conditions:
96
+
97
+ The above copyright notice and this permission notice shall be
98
+ included in all copies or substantial portions of the Software.
99
+
100
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
101
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
102
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
103
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
104
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
105
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
106
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+ require 'rdoc/task'
4
+
5
+ # Default directory to look in is `/specs`
6
+ # Run with `rake spec`
7
+ RSpec::Core::RakeTask.new(:spec) do |task|
8
+ task.rspec_opts = ['--color', '--format', 'documentation']
9
+ end
10
+
11
+ task :default => :spec
12
+
13
+ RDoc::Task.new(:rdoc => "doc", :clobber_rdoc => "doc:clean", :rerdoc => "doc:force") do |rdoc|
14
+ rdoc.rdoc_dir = 'doc'
15
+ rdoc.title = 'Flat Library Documentation'
16
+ rdoc.main = "README.md"
17
+ rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
18
+ end
data/flat.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'flat/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "flat"
8
+ spec.version = Flat::VERSION
9
+ spec.authors = ["Mel Riffe"]
10
+ spec.email = ["mriffe@gmail.com"]
11
+ spec.summary = %q{Library to make processing Flat Flies as easy as CSV files.}
12
+ spec.description = %q{Easily process flat files with Flat. Specify the format in a subclass of Flat::File and read and write until the cows come home.}
13
+ spec.homepage = "http://rubydoc.info/github/juicyparts/flat"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "extlib", "~> 0.9.0"
22
+
23
+ spec.add_development_dependency "bundler", ">= 1.6.2" # was "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rdoc", "~> 4.1.0"
26
+
27
+ spec.add_development_dependency "rspec", "~> 3.1.0"
28
+ spec.add_development_dependency "rspec-nc", "~> 0.2.0"
29
+ spec.add_development_dependency "guard", "~> 2.6.0"
30
+ spec.add_development_dependency "guard-rspec", "~> 4.3.0"
31
+ spec.add_development_dependency "pry", "~> 0.10.0"
32
+ spec.add_development_dependency "pry-remote", "~> 0.1.0"
33
+ spec.add_development_dependency "pry-nav", "~> 0.2.0"
34
+
35
+ spec.add_development_dependency "coveralls", "~> 0.7.0"
36
+ end
@@ -0,0 +1,33 @@
1
+ module Errors #:nodoc:
2
+
3
+ # = FlatFileError
4
+ #
5
+ # Generic error class and superclass of all other errors raised by Flat.
6
+ #
7
+ class FlatFileError < StandardError; end
8
+
9
+ # = LayoutConstructorError
10
+ #
11
+ # The specified layout definition was not valid.
12
+ #
13
+ class LayoutConstructorError < FlatFileError; end
14
+
15
+ # = RecordLengthError
16
+ #
17
+ # Generic error having to do with line lengths not meeting expectations.
18
+ #
19
+ class RecordLengthError < FlatFileError; end
20
+
21
+ # = ShortRecordError
22
+ #
23
+ # The incoming line was shorter than expections defined.
24
+ #
25
+ class ShortRecordError < RecordLengthError; end
26
+
27
+ # = LongRecordError
28
+ #
29
+ # The incoming line was longer than expections defined.
30
+ #
31
+ class LongRecordError < RecordLengthError; end
32
+
33
+ end # => module Errors
data/lib/flat/field.rb ADDED
@@ -0,0 +1,171 @@
1
+ ##
2
+ # = Field
3
+ #
4
+ # A field definition tracks information that's necessary for
5
+ # FlatFile to process a particular field. This is typically
6
+ # added to a subclass of FlatFile like so:
7
+ #
8
+ # class SomeFile < FlatFile
9
+ # add_field :some_field_name, :width => 35
10
+ # end
11
+ #
12
+ module Field
13
+ ##
14
+ # = Class Methods
15
+ #
16
+ # Defines behavior for subclasses of Flat::File regarding the specification
17
+ # of the line structure contained in a flat file.
18
+ #
19
+ module ClassMethods
20
+
21
+ ##
22
+ # Add a field to the FlatFile subclass. Options can include
23
+ #
24
+ # :width - number of characters in field (default 10)
25
+ # :filter - callack, lambda or code block for processing during reading
26
+ # :formatter - callback, lambda, or code block for processing during writing
27
+ #
28
+ # class SomeFile < FlatFile
29
+ # add_field :some_field_name, :width => 35
30
+ # end
31
+ #
32
+ # == Options
33
+ #
34
+ def add_field name = nil, options = {}, &block
35
+ fields << field_def = Field::Definition.new(name, options, self)
36
+
37
+ yield field_def if block_given?
38
+
39
+ pack_format << "A#{field_def.width}"
40
+ flat_file_data[:width] += field_def.width
41
+ # width += field_def.width # doesn't work for some reason
42
+
43
+ # TODO: Add a check here to ensure the Field has a name specified; it can be a String or Symbol
44
+ return field_def
45
+ end
46
+
47
+ ##
48
+ # Add a pad field. To have the name auto generated, use :autoname for
49
+ # the name parameter. For options see +add_field+.
50
+ #
51
+ def pad name, options = {}
52
+ add_field ( name == :autoname ? new_pad_name : name ), options.merge( padding: true )
53
+ end
54
+
55
+ private
56
+
57
+ ##
58
+ # Used to generate unique names for pad fields which use :autoname.
59
+ #
60
+ def new_pad_name #:nodoc:
61
+ "pad_#{unique_id}".to_sym
62
+ end
63
+
64
+ ##
65
+ # Increments an id counter which is used to generate unique pad names
66
+ #
67
+ def unique_id #:nodoc:
68
+ @unique_id = (@unique_id || 0) + 1
69
+ end
70
+
71
+ end # => module ClassMethods
72
+
73
+ module InstanceMethods #:nodoc:
74
+ end # => module InstanceMethods
75
+
76
+ def self.included receiver #:nodoc:
77
+ receiver.extend ClassMethods
78
+ receiver.send :include, InstanceMethods
79
+ end
80
+
81
+ # = Definition
82
+ # Used in Flat::File subclasses to define how a Record is defined.
83
+ #
84
+ # class SomeFile < Flat::File
85
+ # add_field :some_field_name, :width => 35
86
+ # end
87
+ #
88
+ class Definition #:nodoc:
89
+ attr_accessor :parent, :name, :width, :padding, :aggressive
90
+ attr_accessor :filters, :formatters, :map_in_proc
91
+
92
+ ##
93
+ # Create a new FieldDef, having name and width. parent is a reference to the
94
+ # FlatFile subclass that contains this field definition. This reference is
95
+ # needed when calling filters if they are specified using a symbol.
96
+ #
97
+ # Options can be :padding (if present and a true value, field is marked as a
98
+ # pad field), :width, specify the field width, :formatter, specify a formatter,
99
+ # :filter, specify a filter.
100
+ #
101
+ def initialize name = null, options = {}, parent = {}
102
+ @parent, @name = parent, name
103
+
104
+ @width = options.fetch(:width, 10)
105
+ @padding = options.fetch(:padding, false)
106
+ @aggressive = options.fetch(:aggressive, false)
107
+
108
+ @filters = @formatters = Array.new
109
+
110
+ add_filter options[:filter]
111
+ add_formatter options[:formatter]
112
+
113
+ @map_in_proc = options[:map_in_proc]
114
+ end
115
+
116
+ def padding?
117
+ @padding
118
+ end
119
+
120
+ def aggressive?
121
+ @aggressive
122
+ end
123
+
124
+ ##
125
+ # Add a filter. Filters are used for processing field data when a flat file is
126
+ # being processed. For fomratting the data when writing a flat file, see
127
+ # add_formatter
128
+ #
129
+ def add_filter filter = nil, &block
130
+ @filters.push( filter ) unless filter.blank?
131
+ @filters.push( block ) if block_given?
132
+ end
133
+
134
+ ##
135
+ # Add a formatter. Formatters are used for formatting a field
136
+ # for rendering a record, or writing it to a file in the desired format.
137
+ #
138
+ def add_formatter formatter = nil, &block
139
+ @formatters.push( formatter ) unless formatter.blank?
140
+ @formatters.push( block ) if block_given?
141
+ end
142
+
143
+ ##
144
+ # Passes value through the filters defined on this Field::Definition
145
+ #
146
+ def filter value
147
+ @filters.each do |filter|
148
+ value = case filter
149
+ when Symbol
150
+ @parent.public_send filter, value
151
+ when Proc
152
+ if filter.arity == 0
153
+ value
154
+ else
155
+ filter.call value
156
+ end
157
+ when Class, Object
158
+ unless filter.respond_to? 'filter'
159
+ value
160
+ else
161
+ filter.filter value
162
+ end
163
+ else
164
+ value
165
+ end
166
+ end
167
+ value
168
+ end
169
+ end # => class Definition
170
+
171
+ end # => module Field
data/lib/flat/file.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'flat/errors'
2
+ require 'flat/file_data'
3
+ require 'flat/field'
4
+ require 'flat/layout'
5
+ require 'flat/record'
6
+ require 'flat/read_operations'
7
+
8
+ # = Flat::File
9
+ #
10
+ class Flat::File
11
+ include Errors
12
+ include FileData
13
+ include Layout
14
+ include Field
15
+ include Record
16
+ include ReadOperations
17
+
18
+ end # => class Flat::File
@@ -0,0 +1,123 @@
1
+ ##
2
+ # = FileData
3
+ #
4
+ # Defines structures and accessors for the internal FileData required by Flat::File
5
+ # and its successful operations.
6
+ #
7
+ module FileData
8
+ ##
9
+ # = Class Methods
10
+ #
11
+ # Defines behavior for subclasses of Flat::File regarding the management of
12
+ # its internal data structures.
13
+ #
14
+ module ClassMethods
15
+
16
+ ##
17
+ # Container for various points of data as defined in the Flat::File subclass.
18
+ #
19
+ # Returns a +Hash+ with the following keys:
20
+ # * +:width+ - The overall width, or length, of a line in the flat file.
21
+ # * +:pack_format+ - A format +String+ for use by String#unpack.
22
+ # * +:fields+ - An +Array+ of Field::Definitions
23
+ # * +:layouts+ - An +Array+ of Layout::Definitions
24
+ #
25
+ def flat_file_data
26
+ @data ||= {
27
+ :width => 0,
28
+ :pack_format => '',
29
+ :fields => [],
30
+ :layouts => [],
31
+ }
32
+ end
33
+
34
+ ##
35
+ # Convenience method for accessing <tt>flat_file_data[:fields]</tt>
36
+ #
37
+ # Returns an +Array+ of Field::Definitions
38
+ #
39
+ def fields
40
+ flat_file_data[:fields]
41
+ end
42
+
43
+ ##
44
+ # Convenience method for accessing <tt>flat_file_data[:width]</tt>
45
+ #
46
+ # Returns the overall length of a line to text in the flat file.
47
+ #
48
+ def width
49
+ flat_file_data[:width]
50
+ end
51
+
52
+ #
53
+ # Added setter to support +=, -= constructs; also allows direct
54
+ # assignment.
55
+ #
56
+ def width=(value) #:nodoc:
57
+ flat_file_data[:width] = value
58
+ end
59
+
60
+ ##
61
+ # Convenience method for accessing <tt>flat_file_data[:pack_format]</tt>
62
+ #
63
+ # Returns a +String+ sutiable for use with String#unpack
64
+ #
65
+ def pack_format
66
+ flat_file_data[:pack_format]
67
+ end
68
+
69
+ ##
70
+ # Convenience method for accessing <tt>flat_file_data[:layouts]</tt>
71
+ #
72
+ # Returns an +Array+ of Layout::Definitions
73
+ #
74
+ def layouts
75
+ flat_file_data[:layout]
76
+ end
77
+
78
+ #
79
+ # Added for test purposes, primarily. DESTRUCTIVE!
80
+ #
81
+ def reset_file_data #:nodoc:
82
+ @data = nil
83
+ end
84
+
85
+ end # => module ClassMethods
86
+
87
+ ##
88
+ # = Instance Methods
89
+ #
90
+ # Defines behavior for instances of subclasses of Flat::File regarding the
91
+ # accessing of its internal data structures.
92
+ #
93
+ module InstanceMethods
94
+
95
+ ##
96
+ # Returns the +width+ of this Flat::File subclass as defined in its class
97
+ #
98
+ def width
99
+ self.class.width
100
+ end
101
+
102
+ ##
103
+ # Returns the +pack_format+ of this Flat::File subclass as defined in its class
104
+ #
105
+ def pack_format
106
+ self.class.pack_format
107
+ end
108
+
109
+ ##
110
+ # Returns the +fields+ of this Flat::File subclass as defined in its class
111
+ #
112
+ def fields
113
+ self.class.fields
114
+ end
115
+
116
+ end # => module InstanceMethods
117
+
118
+ def self.included receiver #:nodoc:
119
+ receiver.extend ClassMethods
120
+ receiver.send :include, InstanceMethods
121
+ end
122
+
123
+ end # => module FileData
@@ -0,0 +1,30 @@
1
+ # = Layout
2
+ # *EXPERIMENTAL*
3
+ #
4
+ # If a flat file contains several different record structures, defining
5
+ # more than one Layout::Definition allows Flat::File to easily process
6
+ # the file.
7
+ #
8
+ # *EXPERIMENTAL*
9
+ #
10
+ module Layout
11
+ module ClassMethods #:nodoc:
12
+ end # => module ClassMethods
13
+
14
+ module InstanceMethods #:nodoc:
15
+ end # => module InstanceMethods
16
+
17
+ def self.included receiver #:nodoc:
18
+ receiver.extend ClassMethods
19
+ receiver.send :include, InstanceMethods
20
+ end
21
+
22
+ # = Definition
23
+ # Add the ability to have multiple layouts per flat flat.
24
+ #
25
+ # EXPERIMENTAL
26
+ #
27
+ class Definition #:nodoc:
28
+ end # => class Definition
29
+
30
+ end # => module Layout
@@ -0,0 +1,50 @@
1
+ ##
2
+ # = ReadOperations
3
+ #
4
+ # Defines functionality required for the successful reading, parsing and
5
+ # interpreting of a line of text contained in a flat file.
6
+ #
7
+ module ReadOperations
8
+ module ClassMethods #:nodoc:
9
+ end # => module ClassMethods
10
+
11
+ ##
12
+ # = Instance Methods
13
+ #
14
+ # Defines behavior for instances of a subclass of Flat::File regarding the
15
+ # reading and parsing of the file contents, line by line.
16
+ #
17
+ module InstanceMethods
18
+
19
+ # Iterate through each record (each line of the data file). The passed
20
+ # block is passed a new Record representing the line.
21
+ #
22
+ # s = SomeFile.new
23
+ # s.each_record(open('/path/to/file')) do |r|
24
+ # puts r.first_name
25
+ # end
26
+ #--
27
+ # NOTE: Expects an open valid IO handle; opening and closing is out of scope
28
+ #++
29
+ #
30
+ def each_record io, &block
31
+ io.each_line do |line|
32
+ line.chop!
33
+ next if line.length.zero?
34
+
35
+ unless (self.width - line.length).zero?
36
+ raise Errors::RecordLengthError, "length is #{line.length} but should be #{self.width}"
37
+ end
38
+
39
+ yield create_record(line, io.lineno), line
40
+ end
41
+ end
42
+
43
+ end # => module InstanceMethods
44
+
45
+ def self.included receiver #:nodoc:
46
+ receiver.extend ClassMethods
47
+ receiver.send :include, InstanceMethods
48
+ end
49
+
50
+ end # => module ReadOperations