flat 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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