easy_attributes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Allen Fair
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,156 @@
1
+ = easy_attributes
2
+
3
+ Easy Attributes is a Ruby DSL to give more control to attributes. Tested with ruby 1.87 & 1.9.2
4
+
5
+ == Usage
6
+
7
+ Easy Attributes works in any Ruby class, it does not require Active Record or other ORM.
8
+
9
+ First, install this from rubygems.org
10
+ gem install easy_attributes
11
+
12
+ Next, you need to configure your app to use it, by either:
13
+ require 'rubygems' # Rails application, no framework
14
+ require 'easy_attributes' #
15
+
16
+ config.gem 'easy_attributes' # Rails 2.x, in your ./config/environment.rb
17
+
18
+ gem 'easy_attributes' # Rails 3.x, in your ./Gemfile
19
+
20
+ Configuration:
21
+ EasyAttributes::Config.orm = :active_model # will generate some validations, etc.
22
+ EasyAttributes::Config.load(filename) # Loads a file of column/value/names/descriptions
23
+ EasyAttributes::define(attribute_name, {:name=>value, ...})
24
+
25
+ Declaration:
26
+
27
+ class Record < ObjectRelationalMapperOfChoice
28
+ include EasyAttributes
29
+
30
+ attr_values :field, :name=>'value', :name=>'value', ... :validate=>:active_record, :required=>true, :like=>'other' ...
31
+ attr_sequence :field, :name, :name, ... ,:start=>1, :step=>1
32
+ attr_money :amount, :units=>"dollars", :unit=>'$', :negative=>'%.2f CR'
33
+ attr_bytes :bandwidth
34
+ end
35
+
36
+ == attr_values
37
+
38
+ will define:
39
+ field() # => value ### if orm == :attr, attr_accessor will be installed for the attribute
40
+ field=(value) # => value
41
+ field_sym() # => :name
42
+ field_sym=(symbol) # => value
43
+ field_values() # => {:name=>value, ...}
44
+ field_is?(:name) # true if :name matches current value
45
+ field_is?(:name, :name, ...) # true if value matches a given symbol
46
+ field_is?(:greater_than, :name) # true if the value is greater than the value of :name
47
+ The first argument can be one of:
48
+ :greater_than, :gt, :greater_than_or_equal, :ge, :less_than, :lt, :less_than_or_equal, :le
49
+ field_is?(:between, :name1, :name2) # true if the value is between the corresponding values
50
+
51
+ == attr_sequence
52
+
53
+ sets up a attr_values with the sequential numbering, by default starting at 1 incrementing by 1
54
+
55
+ == attr_bytes
56
+
57
+ Creates helper fields to display and accept byte quantities as KB, MB, GB, TB, etc. Since a kilobyte (KB) is
58
+ now defined as 1000 bytes, and a kibibyte (KiB) is the 1024 quantity, you wan to configure which you want to use
59
+
60
+ EasyAttributes::Config.kb_size = 1000 # Uses KB units as 1KB = 1000 bytes
61
+ EasyAttributes::Config.kb_size = 1024 # Uses KB units as 1KB = 1024 bytes
62
+ EasyAttributes::Config.kb_size = :new # Uses KiB units as 1KiB = 1024 bytes (DEFAULT)
63
+ EasyAttributes::Config.kb_size = :both # Combines usage of KB (1000) and KiB (1024) units (Watch out!)
64
+
65
+ attr_bytes creates the following methods for a :bandwidth attribute
66
+
67
+ bandwidth_bytes = "1 KB" # => 1024
68
+ bandwidth_bytes # => "1 KB" using whatever unit is best
69
+ bandwidth_bytes(:MiB) # => "12345.67 MB" using the specified unit
70
+ bandwidth_bytes(:MiB, :precision=>0) # => "123 MB"
71
+
72
+ == attr_money
73
+
74
+ is the inclusion of my easy_money gem
75
+
76
+ Mixin the EasyAttributes module into the (model) class you need, and declare the
77
+ attributes (columns) you wish you have the attr_money helpers
78
+
79
+ class Ledger
80
+ include EasyAttributes
81
+ attr_accessor :amount, :euro # Integer value of cents
82
+ attr_money :amount # Creates amount_money() and amount_money=() methods
83
+ attr_money :amount, :units=>"dollars", :unit=>'$', :negative=>'%.2f CR'
84
+ # Creates amount_dollars() and amount_dollars=() methods
85
+ end
86
+
87
+ ledger = Ledger.new
88
+ ledger.amount = 100 # 100 cents = $1.00
89
+ ledger.amount_money #=> "1.00"
90
+ ledger.amount_money = "-123.45"
91
+ ledger.amount #=> -12345 (cents)
92
+ ledger.amount_money(:negative=>'%.2f CR', :zero=>'Free') # Uses these formats
93
+ ledger.amount_dollars #=> "$123.45 CR"
94
+
95
+ # Track the bets of the Gamesters of Triskelion on their drill thrall competitions.
96
+ class ProviderWagers < ActiveRecord::Base
97
+ include EasyAttributes
98
+ attr_money :quatloos, :units=>'quatloos', :precision=>3,
99
+ :zero=>'even', :nil=>'no bet', :negative=>'%.3f Loss', :unit=>'Q',
100
+ :negative_regex=>/^(-?)(.+\d)\s*Loss/i
101
+ # creates amount_quatloos(), amount_quatloos=()
102
+ end
103
+
104
+ # in your views
105
+ <%= f.text_field :amount_quatloos %> # -12000 => "Q12.000 Loss"
106
+
107
+ Options for attr_money calls:
108
+ * :money_method - Use this as the alternative name to the money-access methods
109
+ * :units - Use this as an alternative suffix name to the money methods ('dollars' gives 'xx_dollars')
110
+ * :precision - The number of digits implied after the decimal, default is 2
111
+ * :separator - The character to use after the integer part, default is '.'
112
+ * :delimiter - The character to use between every 3 digits of the integer part, default none
113
+ * :positive - The sprintf format to use for positive numbers, default is based on precision
114
+ * :negative - The sprintf format to use for negative numbers, default is same as :positive
115
+ * :zero - The sprintf format to use for zero, default is same as :positive
116
+ * :nil - The sprintf format to use for nil values, default none
117
+ * :unit - Prepend this to the front of the money value, say '$', default none
118
+ * :blank - Return this value when the money string is empty or has no digits on assignment
119
+ * :negative_regex - A Regular Expression used to determine if a number is negative (and without a - sign), defaults to having a "CR" after the number
120
+
121
+ === Formatters
122
+ You can also call or build your own custom conversions. Ensure that
123
+ you can convert between the integer and money forms if you need to.
124
+
125
+ The "money" type is a string, suitable for human editing, and will convert back into
126
+ integer type. If you override the formatting options, test that your money result
127
+ string will convert properly back to the original integer value.
128
+
129
+ include EasyAttributes
130
+ ...
131
+ puts EasyAttributes.money_to_integer( money_string, :option=>value, ... )
132
+ puts EasyAttributes.integer_to_money( cents_integer, :option=>value, ... )
133
+ puts EasyAttributes.integer_to_float( cents_integer, :option=>value, ... )
134
+ puts EasyAttributes.float_to_integer( money_float, :option=>value, ... )
135
+
136
+ EasyAttributes.integer_to_float( nil, blank:0 ) #=> 0.0 [Ruby 1.9.1 Syntax]
137
+ EasyAttributes.integer_to_float( 12345, :precision=>3 ) #=> 12.345
138
+ EasyAttributes.float_to_integer(12.345111, :precision=>3 ) #=> 12345
139
+
140
+ The options to these methods are the same as the #attr_money declarations
141
+
142
+
143
+ == Note on Patches/Pull Requests
144
+
145
+ * Fork the project.
146
+ * Make your feature addition or bug fix.
147
+ * Add tests for it. This is important so I don't break it in a
148
+ future version unintentionally.
149
+ * Commit, do not mess with rakefile, version, or history.
150
+ (if you want to have your own version, that is fine but
151
+ bump version in a commit by itself I can ignore when I pull)
152
+ * Send me a pull request. Bonus points for topic branches.
153
+
154
+ == Copyright
155
+
156
+ Copyright (c) 2010 Allen Fair. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "easy_attributes"
8
+ gem.summary = %Q{Easy Attributes for Ruby}
9
+ gem.description = %Q{Easy Attributes is a Ruby DSL to give more control to attributes.}
10
+ gem.email = "allen.fair@gmail.com"
11
+ gem.homepage = "http://github.com/afair/easy_attributes"
12
+ gem.authors = ["Allen Fair"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/test_*.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "easy_attributes #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,51 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{easy_attributes}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Allen Fair"]
12
+ s.date = %q{2010-04-28}
13
+ s.description = %q{Easy Attributes is a Ruby DSL to give more control to attributes.}
14
+ s.email = %q{allen.fair@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "easy_attributes.gemspec",
27
+ "lib/easy_attributes.rb",
28
+ "test/helper.rb",
29
+ "test/test_easy_attributes.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/afair/easy_attributes}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.6}
35
+ s.summary = %q{Easy Attributes for Ruby}
36
+ s.test_files = [
37
+ "test/helper.rb",
38
+ "test/test_easy_attributes.rb"
39
+ ]
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ else
47
+ end
48
+ else
49
+ end
50
+ end
51
+
@@ -0,0 +1,364 @@
1
+ # Mixes in attr_* definitions into ruby classes for using symbolic names as values, money, and bytes
2
+ module EasyAttributes
3
+
4
+ def self.included(base) #:nodoc:
5
+ base.extend( ClassMethods )
6
+ #EasyAttributes::Config
7
+ #puts "easy_attributes included into #{base}"
8
+ end
9
+
10
+ # Configuration class for EasyAttributes, set at load time.
11
+ class Config
12
+ @@orm = :attr # :attr, :active_model
13
+ @@attributes = {}
14
+ @@values = {}
15
+ @@kb_size=:iec
16
+
17
+ # Values: :old, :jedec, or 1024 uses KB=1024
18
+ # :new, :iec uses KiB=1024, no KB
19
+ # 1000, :decimal uses only KB (1000) (other values mix KB and KiB units)
20
+ def self.kb_size=(b)
21
+ @@kb_size = b
22
+ end
23
+
24
+ # Returns the kb_size setting
25
+ def self.kb_size
26
+ @@kb_size
27
+ end
28
+
29
+ # Set the ORM or attribute manager, currently to :attr (attr_accessor) or :active_model
30
+ def self.orm=(o)
31
+ @@orm = o
32
+ end
33
+
34
+ # Returns the ORM setting
35
+ def self.orm
36
+ @@orm
37
+ end
38
+
39
+ # Defines a symbol name to a hash of :name=>value
40
+ def self.define(name, hash)
41
+ @@attributes[name] = hash
42
+ end
43
+
44
+ # Returns the symbol table
45
+ def self.attributes
46
+ @@attributes
47
+ end
48
+
49
+ # Loads a tab-delimted filename into the symbol table in format:
50
+ # attribute value role symbolic_name short_description long_description (useful for "<option>" data)
51
+ # If a block is given, it should parse a file line and return an array of
52
+ # [attribute, value, role*, name, short*, long_description*], *denotes not required, but placeholder required
53
+ def self.load(filename)
54
+ File.open(filename).each do |r|
55
+ next unless r =~ /^\w/
56
+ (col, val, priv, symbol, word, desc) = block_given? ? yield(r) : r.split(/\t/)
57
+ next if desc.nil? || desc.empty? || symbol.empty? || word.empty? || symbol.nil?
58
+ col = col.to_sym
59
+ @@values[col] = {:sym=>{}, :val=>{}, :rec=>{}} unless @@values.has_key?(col)
60
+ @@values[col][:sym][symbol.to_sym] = val.to_i
61
+ @@values[col][:val][val] = symbol.to_sym
62
+ @@values[col][:rec][symbol.to_sym] = {:word=>word, :description=>desc, :value=>val.to_i}
63
+ @@attributes[col.to_s] ||= {}
64
+ @@attributes[col.to_s][symbol.to_sym] = val.to_i
65
+ end
66
+ end
67
+ end
68
+
69
+ module ClassMethods
70
+ # Defines an attribute as a sequence of names. :name1=>1, :name2=>2, etc.
71
+ # attr_sequence :attr, :name, ... :start=>n, :step=>:n
72
+ def attr_sequence(attribute, *names)
73
+ opt = EasyAttributes.pop_options(names)
74
+ values = {}
75
+ names.inject(opt[:start]||1) { |seq, n| values[n]=seq; seq+=(opt[:step]||1)}
76
+ attr_values( attribute, values, opt)
77
+ end
78
+
79
+ # Defines an attribute as a hash like {:key=>value,...} where key names are used interchangably with values
80
+ def attr_values(attribute, *args)
81
+ opt = args.size > 1 ? args.pop : {}
82
+ hash = args.first
83
+
84
+ # Use :like=>colname to copy from another definition (ClassName#attribute) or from the loaded table columns
85
+ if opt[:like]
86
+ hash = EasyAttributes::Config.attributes[opt[:like]]
87
+ end
88
+
89
+ name = "#{self.name}##{attribute}"
90
+ EasyAttributes.add_definition( name, hash)
91
+ code = ''
92
+ if EasyAttributes::Config.orm == :active_model
93
+ validates_inclusion_of attribute, :in=>hash.values
94
+ else
95
+ attr_accessor attribute
96
+ end
97
+ code += %Q(
98
+ def #{attribute}_sym=(v)
99
+ self.#{attribute} = EasyAttributes.value_for_sym("#{name}", v)
100
+ end
101
+ def #{attribute}_sym
102
+ EasyAttributes.sym_for_value("#{name}", #{attribute})
103
+ end
104
+ def self.#{attribute}_values
105
+ EasyAttributes::Config.attributes["#{name}"]
106
+ end
107
+ def #{attribute}_is?(*args)
108
+ EasyAttributes.value_is?("#{name}", attribute, *args)
109
+ end
110
+ )
111
+ #puts code
112
+ class_eval code
113
+ end
114
+
115
+ # attr_bytes allows manipultion and display as kb, mb, gb, tb, pb
116
+ # Adds method: attribute_bytes=() and attribute_bytes(:unit, :option=>value )
117
+ def attr_bytes(attribute, *args)
118
+ name = "#{self.name}##{attribute}"
119
+ opt = EasyAttributes.pop_options(args)
120
+ #getter, setter = EasyAttributes.getter_setter(attribute)
121
+ attr_accessor attribute if EasyAttributes::Config.orm == :attr
122
+ code = %Q(
123
+ def #{attribute}_bytes(*args)
124
+ args.size == 0 ? v : EasyAttributes::format_bytes(self.#{attribute}, *args)
125
+ end
126
+ def #{attribute}_bytes=(v)
127
+ self.#{attribute} = EasyAttributes.parse_bytes(v)
128
+ end
129
+ )
130
+ #puts code
131
+ class_eval code
132
+ end
133
+
134
+ # Creates an money instance method for the given method, named "#{attribute}_money" which returns
135
+ # a formatted money string, and a #{attribute}_money= method used to set an edited money string.
136
+ # The original method stores the value as integer (cents, or other precision/currency setting). Options:
137
+ # * :money_method - Use this as the alternative name to the money-access methods
138
+ # * :units - Use this as an alternative suffix name to the money methods ('dollars' gives 'xx_dollars')
139
+ # * :precision - The number of digits implied after the decimal, default is 2
140
+ # * :separator - The character to use after the integer part, default is '.'
141
+ # * :delimiter - The character to use between every 3 digits of the integer part, default none
142
+ # * :positive - The sprintf format to use for positive numbers, default is based on precision
143
+ # * :negative - The sprintf format to use for negative numbers, default is same as :positive
144
+ # * :zero - The sprintf format to use for zero, default is same as :positive
145
+ # * :nil - The sprintf format to use for nil values, default none
146
+ # * :unit - Prepend this to the front of the money value, say '$', default none
147
+ # * :blank - Return this value when the money string is empty or has no digits on assignment
148
+ # * :negative_regex - A Regular Expression used to determine if a number is negative (and without a - sign)
149
+ #
150
+ def attr_money(attribute, *args)
151
+ opt = args.last.is_a?(Hash) ? args.pop : {}
152
+ money_method = opt.delete(:money_method) || "#{attribute}_#{opt.delete(:units)||'money'}"
153
+
154
+ class_eval %Q(
155
+ def #{money_method}(*args)
156
+ opt = args.last.is_a?(Hash) ? args.pop : {}
157
+ EasyAttributes.integer_to_money( #{attribute}, #{opt.inspect}.merge(opt))
158
+ end
159
+
160
+ def #{money_method}=(v, *args)
161
+ opt = args.last.is_a?(Hash) ? args.pop : {}
162
+ self.#{attribute} = EasyAttributes.money_to_integer( v, #{opt.inspect}.merge(opt))
163
+ end
164
+ )
165
+ end
166
+
167
+ end
168
+
169
+ # Returns a [getter_code, setter_code] depending on the orm configuration
170
+ def self.getter_setter(attribute)
171
+ if EasyAttributes::Config.orm == :active_model
172
+ ["self.attributes[:#{attribute}]", "self.write_attribute(:#{attribute}, v)"]
173
+ else
174
+ ["@#{attribute}", "@#{attribute} = v"]
175
+ end
176
+ end
177
+
178
+ def self.pop_options(args, defaults={})
179
+ args.last.is_a?(Hash) ? defaults.merge(args.pop) : defaults
180
+ end
181
+
182
+ def self.add_definition(attribute, hash, opt={})
183
+ EasyAttributes::Config.define attribute, hash
184
+ end
185
+
186
+ ##############################################################################
187
+ # attr_values helpers
188
+ ##############################################################################
189
+
190
+ # Returns the defined value for the given symbol and attribute
191
+ def self.value_for_sym(attribute, sym)
192
+ EasyAttributes::Config.attributes[attribute][sym]
193
+ end
194
+
195
+ # Returns the defined symbol for the given value on the attribute
196
+ def self.sym_for_value(attribute, value)
197
+ EasyAttributes::Config.attributes[attribute].each {|k,v| return k if v==value}
198
+ end
199
+
200
+ def self.value_is?(attribute, value, *args)
201
+ case args.first
202
+ when :between then
203
+ value >= EasyAttributes::Config.attributes[attribute][args[1]] && value <= EasyAttributes::Config.attributes[attribute][args[2]]
204
+ when :gt, :greater_than then
205
+ value > EasyAttributes::Config.attributes[attribute][args[1]]
206
+ when :ge, :greater_than_or_equal_to then
207
+ value >= EasyAttributes::Config.attributes[attribute][args[1]]
208
+ when :lt, :less_than then
209
+ value < EasyAttributes::Config.attributes[attribute][args[1]]
210
+ when :le, :less_than_or_equal_to then
211
+ value <= EasyAttributes::Config.attributes[attribute][args[1]]
212
+ #when :not, :not_in
213
+ # ! args.include? EasyAttributes::Config.attributes[attribute].keys
214
+ else
215
+ args.include? EasyAttributes.sym_for_value(v)
216
+ end
217
+ end
218
+
219
+ ##############################################################################
220
+ # attr_byte helpers
221
+ ##############################################################################
222
+
223
+ # Official Definitions for kilobyte and kibibyte quantity prefixes
224
+ KB =1000; MB =KB **2; GB= KB **3; TB =KB **4; PB =KB **5; EB =KB **6; ZB =KB **7; YB =KB **8
225
+ KiB=1024; MiB=KiB**2; GiB=KiB**3; TiB=KiB**4; PiB=KiB**5; EiB=KiB**6; ZiB=KiB**7; YiB=KiB**8
226
+ DECIMAL_PREFIXES = {:B=>1, :KB=>KB, :MB=>MB, :GB=>GB, :TB=>TB, :PB=>PB, :EB=>EB, :ZB=>ZB, :TB=>YB}
227
+ BINARY_PREFIXES = {:B=>1, :KiB=>KiB, :MiB=>MiB, :GiB=>GiB, :TiB=>TiB, :PiB=>PiB, :EiB=>EiB, :ZiB=>ZiB, :TiB=>YiB}
228
+
229
+ # Returns a hash of prefix names to decimal quantities for the given setting
230
+ def self.byte_prefixes(kb_size=0)
231
+ case kb_size
232
+ when 1000, :decimal, :si then DECIMAL_PREFIXES
233
+ when :old, :jedec, 1024 then {:KB=>KiB, :MB=>MiB, :GB=>GiB, :TB=>TiB, :PB=>PiB, :EB=>EiB, :ZB=>ZiB, :TB=>YiB}
234
+ when :new, :iec then BINARY_PREFIXES
235
+ else DECIMAL_PREFIXES.merge(BINARY_PREFIXES) # Both? What's the least surprise?
236
+ end
237
+ end
238
+
239
+ # Takes a number of bytes and an optional :unit argument and :precision option, returns a formatted string of units
240
+ def self.format_bytes(v, *args)
241
+ opt = EasyAttributes.pop_options(args, :precision=>3)
242
+ prefixes = EasyAttributes.byte_prefixes(opt[:kb_size]||EasyAttributes::Config.kb_size||0)
243
+ if args.size > 0 && args.first.is_a?(Symbol)
244
+ (unit, precision) = args
245
+ v = "%.#{precision||opt[:precision]}f" % (1.0 * v / (prefixes[unit]||1))
246
+ return "#{v} #{unit}"
247
+ else
248
+ precision = args.shift || opt[:precision]
249
+ prefixes.sort{|a,b| a[1]<=>b[1]}.reverse.each do |pv|
250
+ next if pv[1] > v
251
+ v = "%f.10f" % (1.0 * v / pv[1])
252
+ v = v[0,precision+1] if v =~ /^(\d)+\.(\d+)/ && v.size > (precision+1)
253
+ v.gsub!(/\.0*$/, '')
254
+ return "#{v} #{pv[0]}"
255
+ end
256
+ end
257
+ v.to_s
258
+ end
259
+
260
+ # Takes a string of "number units" or Array of [number, :units] and returns the number of bytes represented.
261
+ def self.parse_bytes(v, *args)
262
+ opt = EasyAttributes.pop_options(args, :precision=>3)
263
+ # Handle v= [100, :KB]
264
+ if v.is_a?(Array)
265
+ bytes = v.shift
266
+ v = "#{bytes} #{v.shift}"
267
+ else
268
+ bytes = v.to_f
269
+ end
270
+
271
+ if v.downcase =~ /^\s*(?:[\d\.]+)\s*([kmgtpezy]i?b)/i
272
+ units = ($1.size==2 ? $1.upcase : $1[0,1].upcase+$1[1,1]+$1[2,1].upcase).to_sym
273
+ prefixes = EasyAttributes.byte_prefixes(opt[:kb_size]||EasyAttributes::Config.kb_size||0)
274
+ bytes *= prefixes[units] if prefixes.has_key?(units)
275
+ #puts "v=#{v} b=#{bytes} u=#{units} #{units.class} bp=#{prefixes[units]} kb=#{opt[:kb_size]} P=#{prefixes.inspect}"
276
+ end
277
+ (bytes*100).to_i/100
278
+ end
279
+
280
+ ##############################################################################
281
+ # attr_money helpers
282
+ ##############################################################################
283
+
284
+ # Returns the money string of the given integer value. Uses relevant options from #easy_money
285
+ def self.integer_to_money(value, *args)
286
+ opt = args.last.is_a?(Hash) ? args.pop : {}
287
+ opt[:positive] ||= "%.#{opt[:precision]||2}f"
288
+ pattern =
289
+ if value.nil?
290
+ value = 0
291
+ opt[:nil] || opt[:positive]
292
+ else
293
+ case value <=> 0
294
+ when 1 then opt[:positive]
295
+ when 0 then opt[:zero] || opt[:positive]
296
+ else
297
+ value = -value if opt[:negative] && opt[:negative] != opt[:positive]
298
+ opt[:negative] || opt[:positive]
299
+ end
300
+ end
301
+ value = self.format_money( value, pattern, opt)
302
+ value = opt[:unit]+value if opt[:unit]
303
+ value.gsub!(/\./,opt[:separator]) if opt[:separator]
304
+ if opt[:delimiter] && (m = value.match(/^(\D*)(\d+)(.*)/))
305
+ # Adapted From Rails' ActionView::Helpers::NumberHelper
306
+ n = m[2].gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{opt[:delimiter]}")
307
+ value=m[1]+n+m[3]
308
+ end
309
+ value
310
+ end
311
+
312
+ def self.integer_to_float(value, *args)
313
+ opt = args.last.is_a?(Hash) ? args.pop : {}
314
+ return (opt[:blank]||nil) if value.nil?
315
+ value = 1.0 * value / (10**(opt[:precision]||2))
316
+ end
317
+
318
+ # Returns the integer of the given money string. Uses relevant options from #easy_money
319
+ def self.money_to_integer(value, *args)
320
+ opt = args.last.is_a?(Hash) ? args.pop : {}
321
+ value = value.gsub(opt[:delimiter],'') if opt[:delimiter]
322
+ value = value.gsub(opt[:separator],'.') if opt[:separator]
323
+ value = value.gsub(/^[^\d\.\-\,]+/,'')
324
+ return (opt[:blank]||nil) unless value =~ /\d/
325
+ m = value.to_s.match(opt[:negative_regex]||/^(-?)(.+\d)\s*cr/i)
326
+ value = value.match(/^-/) ? m[2] : "-#{m[2]}" if m && m[2]
327
+
328
+ # Money string ("123.45") to proper integer withough passing through the float transformation
329
+ match = value.match(/(-?\d*)\.?(\d*)/)
330
+ return 0 unless match
331
+ value = match[1].to_i * (10 ** (opt[:precision]||2))
332
+ cents = match[2]
333
+ cents = cents + '0' while cents.length < (opt[:precision]||2)
334
+ cents = cents.to_s[0,opt[:precision]||2]
335
+ value += cents.to_i * (value<0 ? -1 : 1)
336
+ value
337
+ end
338
+
339
+ # Returns the integer (cents) value from a Float
340
+ def self.float_to_integer(value, *args)
341
+ opt = args.last.is_a?(Hash) ? args.pop : {}
342
+ return (opt[:blank]||nil) if value.nil?
343
+ value = (value.to_f*(10**((opt[:precision]||2)+1))).to_i/10 # helps rounding 4.56 -> 455 ouch!
344
+ end
345
+
346
+ # Replacing the sprintf function to deal with money as float. "... %[flags]m ..."
347
+ def self.format_money(value, pattern="%.2m", *args)
348
+ opt = args.last.is_a?(Hash) ? args.pop : {}
349
+ sign = value < 0 ? -1 : 1
350
+ dollars, cents = value.abs.divmod( 10 ** (opt[:precision]||2))
351
+ dollars *= sign
352
+ parts = pattern.match(/^(.*)%([-\. \d+0]*)[fm](.*)/)
353
+ return pattern unless parts
354
+ intdec = parts[2].match(/(.*)\.(\d*)/)
355
+ dprec, cprec = intdec ? [intdec[1], intdec[2]] : ['', '']
356
+ dollars = sprintf("%#{dprec}d", dollars)
357
+ cents = '0' + cents.to_s while cents.to_s.length < (opt[:precision]||2)
358
+ cents = cents.to_s[0,cprec.to_i]
359
+ cents = cents + '0' while cents.length < cprec.to_i
360
+ value = parts[1] + "#{(dollars.to_i==0 && sign==-1) ? '-' : '' }#{dollars}#{cents>' '? '.':''}#{cents}" + parts[3]
361
+ value
362
+ end
363
+
364
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'easy_attributes'
7
+
8
+ class Test::Unit::TestCase
9
+ end
10
+ require 'rubygems'
11
+ require 'test/unit'
12
+
13
+
14
+ class Sample
15
+ include EasyAttributes
16
+ attr_accessor :price, :balance
17
+ attr_money :price
18
+ attr_money :balance
19
+ end
@@ -0,0 +1,147 @@
1
+ require 'helper'
2
+ EasyAttributes::Config.orm = :attr
3
+
4
+ class TestEasyAttributes < Test::Unit::TestCase
5
+ include EasyAttributes
6
+ attr_sequence :tas, :n1, :n2
7
+ attr_values :tav, :k1=>1, :k2=>2, :k3=>3
8
+ attr_values :status, {}, :like=>'TestEasyAttributes#tav'
9
+ attr_bytes :bw
10
+ attr_money :amount
11
+
12
+ def test_attr_sequence
13
+ self.tas_sym = :n1
14
+ assert_equal self.tas, 1
15
+ assert_equal self.tas_sym, :n1
16
+ end
17
+
18
+ def test_attr_values
19
+ self.tav_sym = :k1
20
+ assert_equal tav, 1
21
+ self.tav_sym = :k2
22
+ assert_equal tav, 2
23
+ assert_equal tav_sym, :k2
24
+ end
25
+
26
+ def test_like
27
+ self.status_sym = :k1
28
+ assert_equal self.status, 1
29
+ end
30
+
31
+ # Removed for now, not shipping my data file!
32
+ def test_load
33
+ #EasyAttributes::Config.load "values"
34
+ #assert_equal EasyAttributes::Config.attributes['status'][:ok], 8
35
+ end
36
+
37
+ def test_attr_bytes
38
+ self.bw = 1024
39
+ assert_equal bw, 1024
40
+ assert_equal bw_bytes(:KiB, :precision=>0), "1 KiB"
41
+ self.bw = [1, :kb]
42
+ end
43
+
44
+ def test_format_bytes
45
+ EasyAttributes::Config.kb_size = :both
46
+ assert_equal EasyAttributes.format_bytes( 900 ), "900 B"
47
+ assert_equal EasyAttributes.format_bytes( 1000 ), "1 KB"
48
+ assert_equal EasyAttributes.format_bytes( 12345 ), "12 KiB"
49
+ assert_equal EasyAttributes.format_bytes( 123456789 ), "117 MiB"
50
+ assert_equal EasyAttributes.format_bytes( 9999999999 ), "9.31 GiB"
51
+ assert_equal EasyAttributes.format_bytes( 123456789, :KiB ), "120563.271 KiB"
52
+ assert_equal EasyAttributes.format_bytes( 123456789, :KiB, 1 ), "120563.3 KiB"
53
+ assert_equal EasyAttributes.format_bytes( 123456789, :KiB, 0 ), "120563 KiB"
54
+ end
55
+
56
+ def test_parse_bytes
57
+ EasyAttributes::Config.kb_size=:both
58
+ assert_equal EasyAttributes.parse_bytes( "1.5 KiB" ), 1536
59
+ assert_equal EasyAttributes.parse_bytes( "1 gb" ), EasyAttributes::GB
60
+ assert_equal EasyAttributes.parse_bytes( "1kb", :kb_size=>1000 ), 1000
61
+ assert_equal EasyAttributes.parse_bytes( "1kb", :kb_size=>1024 ), 1024
62
+ end
63
+
64
+ def test_include
65
+ sample = Sample.new
66
+ flunk "no price_money method" unless sample.respond_to?(:price_money)
67
+ flunk "no price_money= method" unless sample.respond_to?(:price_money=)
68
+ end
69
+
70
+ def test_method_money
71
+ s = Sample.new
72
+ [ 10000, 123456, 0, -1 -9876 ].each do |p|
73
+ s.price = p
74
+ m = s.price_money
75
+ s.price_money = m
76
+ flunk "Assignment error: p=#{p} m=#{m} price=#{s.price}" unless s.price = p
77
+ end
78
+ end
79
+
80
+ def test_method_money=
81
+ s = Sample.new
82
+ { "0.00"=>0, "12.34"=>1234, "-1.2345"=>-123, "12"=>1200, "4.56CR"=>-456 }.each do |m,p|
83
+ s.price_money = m
84
+ flunk "Assignment error: p=#{p} m=#{m} price=#{s.price}" unless s.price = p
85
+ end
86
+ end
87
+
88
+ def test_integer_to_money
89
+ assert EasyAttributes.integer_to_money(123) == '1.23'
90
+ assert EasyAttributes.integer_to_money(-12333) == '-123.33'
91
+ assert EasyAttributes.integer_to_money(0) == '0.00'
92
+ assert EasyAttributes.integer_to_money(nil, :nil=>'?') == '?'
93
+ assert EasyAttributes.integer_to_money(-1, :negative=>'%.2f CR') == '0.01 CR'
94
+ assert EasyAttributes.integer_to_money(0, :zero=>'free') == 'free'
95
+ assert EasyAttributes.integer_to_money(100, :unit=>'$') == '$1.00'
96
+ assert EasyAttributes.integer_to_money(100, :separator=>',') == '1,00'
97
+ assert EasyAttributes.integer_to_money(12345678900, :separator=>',', :delimiter=>'.') == '123.456.789,00'
98
+ assert EasyAttributes.integer_to_money(333, :precision=>3) == '0.333'
99
+ assert EasyAttributes.integer_to_money(111, :precision=>1) == '11.1'
100
+ assert EasyAttributes.integer_to_money(111, :precision=>0) == '111'
101
+ end
102
+
103
+ def test_money_to_integer
104
+ assert EasyAttributes.money_to_integer('1.23' ) == 123
105
+ assert EasyAttributes.money_to_integer('0.00' ) == 0
106
+ assert EasyAttributes.money_to_integer('-1.23' ) == -123
107
+ assert EasyAttributes.money_to_integer('1.23 CR' ) == -123
108
+ assert EasyAttributes.money_to_integer('$-2.34 CR' ) == 234
109
+ assert EasyAttributes.money_to_integer(' 1.234' ) == 123
110
+ assert EasyAttributes.money_to_integer('$1' ) == 100
111
+ assert EasyAttributes.money_to_integer('1' ) == 100
112
+ assert EasyAttributes.money_to_integer('' ) == nil
113
+ assert EasyAttributes.money_to_integer('1,00', :separator=>',',:delimiter=>'.') == 100
114
+ assert EasyAttributes.money_to_integer('$123.456.789,00 CR', :separator=>',',:delimiter=>'.') == -12345678900
115
+ assert EasyAttributes.money_to_integer('4.44', :precision=>4 ) == 44400
116
+ assert EasyAttributes.money_to_integer('4.44', :precision=>0 ) == 4
117
+ end
118
+
119
+ def test_float_to_integer
120
+ assert EasyAttributes.float_to_integer(1.00 ) == 100
121
+ assert EasyAttributes.float_to_integer(1.001 ) == 100
122
+ assert EasyAttributes.float_to_integer(-1.23 ) == -123
123
+ assert EasyAttributes.float_to_integer(9.0 ) == 900
124
+ assert EasyAttributes.float_to_integer(nil ) == nil
125
+ assert EasyAttributes.float_to_integer(0.00 ) == 0
126
+ end
127
+
128
+ def test_integer_to_float
129
+ assert EasyAttributes.integer_to_float(1 ) == 0.01
130
+ assert EasyAttributes.integer_to_float(0 ) == 0.0
131
+ assert EasyAttributes.integer_to_float(-100 ) == -1.00
132
+ assert EasyAttributes.integer_to_float(nil ) == nil
133
+ assert EasyAttributes.integer_to_float(9999888, :precision=>3 ) == 9999.888
134
+ end
135
+
136
+ def test_format_money
137
+ assert EasyAttributes.format_money(12345) == '123.45'
138
+ assert EasyAttributes.format_money(12345, "%07.2m") == '0000123.45'
139
+ assert EasyAttributes.format_money(12345, "%07.3m") == '0000123.450'
140
+ assert EasyAttributes.format_money(12345, "%m") == '123'
141
+ assert EasyAttributes.format_money(12345, "free") == 'free'
142
+ assert EasyAttributes.format_money(-12345) == '-123.45'
143
+ assert EasyAttributes.format_money(-12345, "%07.1m") == '-000123.4'
144
+ assert EasyAttributes.format_money(-1) == '-0.01'
145
+ end
146
+
147
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easy_attributes
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Allen Fair
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-28 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Easy Attributes is a Ruby DSL to give more control to attributes.
22
+ email: allen.fair@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - LICENSE
29
+ - README.rdoc
30
+ files:
31
+ - .document
32
+ - .gitignore
33
+ - LICENSE
34
+ - README.rdoc
35
+ - Rakefile
36
+ - VERSION
37
+ - easy_attributes.gemspec
38
+ - lib/easy_attributes.rb
39
+ - test/helper.rb
40
+ - test/test_easy_attributes.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/afair/easy_attributes
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
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.6
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Easy Attributes for Ruby
71
+ test_files:
72
+ - test/helper.rb
73
+ - test/test_easy_attributes.rb