easy_attributes 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 31d92f9d96aec2da5924fffa386bef56751300bf
4
+ data.tar.gz: 7640d424ea0cd14b3ce0bf5a03e23a4f92a39603
5
+ SHA512:
6
+ metadata.gz: 67eeae0dbc1159032d636511a332ba950867c44af7bf8ad6f38473e1140ad08ee0f150c3bd156b1b070eced708241867c31980e0a515dcf4ffce5e592d66288b
7
+ data.tar.gz: 3bdc3eda298dfab2fc15d249f75f007c2565990b7f4dcfd6604d636cdb77415179ee2666d316dc8ed055b1038d1c8fac3608ca9e666717c3ec1e18eedc0a8d02
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in easy_attributes.gemspec
4
+ gemspec
@@ -0,0 +1,36 @@
1
+ # EasyAttributes
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/easy_attributes`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'easy_attributes'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install easy_attributes
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/easy_attributes.
36
+
data/Rakefile CHANGED
@@ -1,52 +1,10 @@
1
- require 'rubygems'
2
- require 'rake'
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
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"
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
18
8
  end
19
9
 
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
10
  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
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "easy_attributes"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -1,51 +1,32 @@
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 -*-
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'easy_attributes/version'
5
5
 
6
- Gem::Specification.new do |s|
7
- s.name = %q{easy_attributes}
8
- s.version = "0.1.3"
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "easy_attributes"
8
+ spec.version = EasyAttributes::VERSION
9
+ spec.authors = ["Allen Fair"]
10
+ spec.email = ["allen.fair@gmail.com"]
9
11
 
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-08-06}
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.7}
35
- s.summary = %q{Easy Attributes for Ruby}
36
- s.test_files = [
37
- "test/helper.rb",
38
- "test/test_easy_attributes.rb"
39
- ]
12
+ spec.summary = %q{Easy Attributes for Ruby: Enum, Bytes, Money, float-as-integer views and forms}
13
+ spec.description = %q{Easy Attributes is a Ruby DSL to give more control to attributes. It provides a unique attribute enum setup, and conversions to bytes and float-as-integer (money, frequencies, Ratings, etc.) / fixed-decimal precision as integer (See the easy_money gem).}
14
+ spec.homepage = "https://github.com/afair/easy_attributes"
40
15
 
41
- if s.respond_to? :specification_version then
42
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
- s.specification_version = 3
16
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
17
+ # delete this section to allow pushing this gem to any host.
18
+ #if spec.respond_to?(:metadata)
19
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
20
+ #else
21
+ # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
+ #end
44
23
 
45
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
46
- else
47
- end
48
- else
49
- end
50
- end
24
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
51
28
 
29
+ spec.add_development_dependency "bundler", "~> 1.11"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "minitest", "~> 5.0"
32
+ end
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
@@ -1,309 +1,579 @@
1
- # Mixes in attr_* definitions into ruby classes for using symbolic names as values, money, and bytes
1
+ require "easy_attributes/version"
2
+
3
+ ###############################################################################
4
+ # EasyAttributes Module - Provides attribute handling enahancements.
5
+ #
6
+ # Features include:
7
+ #
8
+ # * Attribute Enum support, giving symbolic names for numeric or other values
9
+ # * Provides optional ActiveModel ORM enhancements for validations, etc.
10
+ # * Byte datatype helpers
11
+ # * Fixed-decimal datatype (e.g. Dollars) helpers
12
+ #
13
+ # To Use:
14
+ #
15
+ # * Require the easy_attributes gem if you need.
16
+ # * Mix in the EasyAttributes module into your class.
17
+ # * Load any external enum definitions at application start-up
18
+ # * Define attribute enhancers in your class
19
+ #
20
+ # Gemfile (add entry, then `bundle install`):
21
+ # gem 'easy_attributes'
22
+ # Or manually
23
+ # require 'easy_attributes'
24
+ # Then
25
+ # class MyClass
26
+ # include EasyAttributes
27
+ # attr_enum :my_attribute, :zero, :one, :two
28
+ # end
29
+ #
30
+ ###############################################################################
2
31
  module EasyAttributes
3
-
32
+
33
+ # Called by Ruby after including to add our ClassMethods to the parent class
4
34
  def self.included(base) #:nodoc:
5
- base.extend( ClassMethods )
35
+ base.extend(ClassMethods)
6
36
  end
7
-
8
- # Configuration class for EasyAttributes, set at load time.
9
- class Config
10
- @@orm = :attr # :attr, :active_model
11
- @@attributes = {}
12
- @@values = {}
13
- @@kb_size=:iec
14
-
15
- # Values: :old, :jedec, or 1024 uses KB=1024
16
- # :new, :iec uses KiB=1024, no KB
17
- # 1000, :decimal uses only KB (1000) (other values mix KB and KiB units)
18
- def self.kb_size=(b)
19
- @@kb_size = b
20
- end
21
-
22
- # Returns the kb_size setting
23
- def self.kb_size
24
- @@kb_size
37
+
38
+ #############################################################################
39
+ # EasyAttributes::Definition - Class of a attribute values definition
40
+ #
41
+ # attribute - symbolic name of the attribute, field, or column.
42
+ # symbols - Hash of {symbolic_value:value, ...}
43
+ # values - Hash of {value => :symbolic_value, ...}
44
+ # options - Hash of {option_name:value} for attribute settings
45
+ # attr_options - Hash of {value_name: {option_name:value, ...}, e.g.
46
+ # :name - Alternate text for the value name
47
+ # :title - Alternate text for value definition
48
+ # :role - Identifier for a role allowed to set/see
49
+ #############################################################################
50
+ class Definition
51
+ include Enumerable
52
+ attr_accessor :attribute, :values, :symbols, :options, :attr_options
53
+
54
+ # Public: Returns an existing or new definition for the atribute name
55
+ # Call this method to define a global or shared setting
56
+ #
57
+ # attribute - The name of the attribute
58
+ # definition - The optional definition list passed to initialize
59
+ # A has of symbol_name => values
60
+ # Make sure the type of value matches your use,
61
+ # either a string "42" or integer "42".to_i
62
+ # attr_options - A optional Hash of attribute names to a hash of additional info
63
+ # {status:{help:"...", title:"Status"}}
64
+ #
65
+ # Examples
66
+ #
67
+ # defn = Definition.find_or_create(:status).add_symbol(:retired, 3)
68
+ # defn = Definition.find_or_create(:status, active:1, inactive:2)
69
+ # defn = Definition.find_or_create(:storage, {}, {kb_size:1000})
70
+ # defn.values #=> {value=>:symbol,...}
71
+ # defn.symbols #=> {:symbol=>:value,...}
72
+ #
73
+ #
74
+ # Returns an existing or new instance of Definition
75
+ #
76
+ def self.find_or_create(attribute, *definition)
77
+ attribute = attribute.to_sym
78
+ @attributes ||= {}
79
+ unless @attributes.has_key?(attribute)
80
+ @attributes[attribute] = Definition.new(attribute, *definition)
81
+ end
82
+ @attributes[attribute]
25
83
  end
26
-
27
- # Set the ORM or attribute manager, currently to :attr (attr_accessor) or :active_model
28
- def self.orm=(o)
29
- @@orm = o
84
+
85
+ def to_s
86
+ "<#EasyAttributes::Definition #{@attribute} #{@symbols.inspect}>"
30
87
  end
31
-
32
- # Returns the ORM setting
33
- def self.orm
34
- @@orm
35
- end
36
-
37
- # Defines a symbol name to a hash of :name=>value
38
- def self.define(name, hash)
39
- @@attributes[name] = hash
40
- end
41
-
42
- # Returns the symbol table
43
- def self.attributes
44
- @@attributes
45
- end
46
-
47
- # Returns the value table:
48
- # values[:name][:rec][:setting] = {:word, :description, :value}
49
- # values[:name][:sym][:setting] = value
50
- # values[:name][:value]["value"] = :symbol
51
- def self.values
52
- @@values
53
- end
54
-
55
- # Loads a tab-delimted filename into the symbol table in format:
56
- # attribute value role symbolic_name short_description long_description (useful for "<option>" data)
57
- # If a block is given, it should parse a file line and return an array of
58
- # [attribute, value, role*, name, short*, long_description*], *denotes not required, but placeholder required
59
- def self.load(filename)
60
- File.open(filename).each do |r|
61
- next unless r =~ /^\w/
62
- (col, val, priv, symbol, word, desc) = block_given? ? yield(r) : r.split(/\t/)
63
- next if desc.nil? || desc.empty? || symbol.empty? || word.empty? || symbol.nil?
64
- col = col.to_sym
65
- @@values[col] = {:sym=>{}, :val=>{}, :rec=>{}} unless @@values.has_key?(col)
66
- @@values[col][:sym][symbol.to_sym] = val.to_i
67
- @@values[col][:val][val] = symbol.to_sym
68
- @@values[col][:rec][symbol.to_sym] = {:word=>word, :description=>desc.chomp, :value=>val.to_i}
69
- @@attributes[col.to_s] ||= {}
70
- @@attributes[col.to_s][symbol.to_sym] = val.to_i
71
- end
88
+
89
+ def self.definitions
90
+ @attributes
72
91
  end
73
- end
74
-
75
- module ClassMethods
76
- # Defines an attribute as a sequence of names. :name1=>1, :name2=>2, etc.
77
- # attr_sequence :attr, :name, ... :start=>n, :step=>:n
78
- def attr_sequence(attribute, *names)
79
- opt = EasyAttributes.pop_options(names)
80
- values = {}
81
- names.inject(opt[:start]||1) { |seq, n| values[n]=seq; seq+=(opt[:step]||1)}
82
- attr_values( attribute, values, opt)
83
- end
84
-
85
- # Defines an attribute as a hash like {:key=>value,...} where key names are used interchangably with values
86
- def attr_values(attribute, *args)
87
- opt = args.size > 1 ? args.pop : {}
88
- hash = args.first
89
-
90
- # Use :like=>colname to copy from another definition (ClassName#attribute) or from the loaded table columns
91
- if opt[:like]
92
- hash = EasyAttributes::Config.attributes[opt[:like]]
93
- end
94
-
95
- name = "#{self.name}##{attribute}"
96
- EasyAttributes.add_definition( name, hash)
97
- code = ''
98
- if EasyAttributes::Config.orm == :active_model && opt[:scope]
99
- validates_inclusion_of attribute, :in=>hash.values
100
- # Add named_scope (scope) for each value
101
- hash.each { |k,v| code += "scope :#{k}, :conditions=>{:#{attribute}=>#{v.inspect}}\n" }
102
- else
103
- attr_accessor attribute
92
+
93
+ def self.shared(attribute, *definition)
94
+ find_or_create(attribute, *definition)
95
+ end
96
+
97
+ # Public: Creates a new Definition for the attribute and definition list
98
+ # Call this method to create a non-shared definition, else call find_or_create
99
+ #
100
+ # Examples
101
+ #
102
+ # Definition.new(:status, active:1, inactive:2)
103
+ #
104
+ # Returns the new instance
105
+ def initialize(attribute, *definition)
106
+ self.attribute = attribute.to_sym
107
+ self.values = {}
108
+ self.symbols = {}
109
+ self.options = {}
110
+ self.attr_options = {}
111
+ self.define(*definition)
112
+ end
113
+
114
+ # Public: Create an attribute definition
115
+ #
116
+ # symbols - Hash of {symbol:value,...} for the attribute, or
117
+ # - Array of enum definitions for the attribute, or
118
+ # - Hash of {value:value, title:text, name:text, option_name:etc}
119
+ # options - Hash of {name:value,...} to track for the attribute. Optional.
120
+ # attr_options: {attribute: {....}, ...}
121
+ #
122
+ # Examples
123
+ #
124
+ # definition.define(active:1, inactive:2)
125
+ # definition.define(:active, :inactive)
126
+ #
127
+ def define(*args)
128
+ return if args.first.nil?
129
+ return define_enum(*args) if args.first.is_a?(Array)
130
+
131
+ symbols = {}
132
+ options = {}
133
+ args.first.each do |k,v|
134
+ if v.is_a?(Hash)
135
+ symbols[k.to_sym] = v.delete(:value) {k.to_s}
136
+ options[k.to_sym] = v
137
+ else
138
+ symbols[k.to_sym] = v
139
+ end
104
140
  end
105
- code += %Q(
106
- def #{attribute}_sym=(v)
107
- self.#{attribute} = EasyAttributes.value_for_sym("#{name}", v)
108
- end
109
- def #{attribute}_sym
110
- EasyAttributes.sym_for_value("#{name}", #{attribute})
111
- end
112
- def self.#{attribute}_values
113
- EasyAttributes::Config.attributes["#{name}"]
114
- end
115
- def #{attribute}_is?(*args)
116
- EasyAttributes.value_is?("#{name}", #{attribute}, *args)
141
+
142
+ self.symbols.merge!(symbols)
143
+ self.values = Hash[* self.symbols.collect {|k,v| [v, k]}.flatten]
144
+ self.attr_options.merge!(options)
145
+ end
146
+
147
+ # Public: Defines an Symbol/Value for the attribute
148
+ #
149
+ # attrib - name of the attribute or database column
150
+ # symbol - internal symbolic name for the value
151
+ # - If nil, it will use the next value from the set of current values
152
+ # value - Value to store in class or database
153
+ # options - Hash of {name:value,...} to track for the symbol. Optional.
154
+ #
155
+ # Examples
156
+ #
157
+ # definition.add_symbol(:active, 1)
158
+ # EasyAttributes::Config.define_value :status, :active, 1, name:"Active"
159
+ #
160
+ def add_symbol(symbol, value=nil, attr_options={})
161
+ symbol = symbol.to_sym
162
+ if value.nil?
163
+ if a.size > 0
164
+ value = self.values.keys.max.next
165
+ else
166
+ value = 0
117
167
  end
118
- )
119
- if EasyAttributes::Config.orm == :active_model
120
- code += %Q(
121
- def #{attribute}=(v)
122
- self[:#{attribute}] = v.is_a?(Symbol) ? EasyAttributes.value_for_sym("#{name}", v) : v;
123
- end
124
- )
125
168
  end
126
- #puts code
127
- class_eval code
169
+
170
+ value = self.values.keys.max || 0 if value.nil?
171
+ self.symbols[symbol] = value
172
+ self.values[value] = symbol
173
+ self.attr_options[symbol] = attr_options
128
174
  end
129
-
130
- # attr_bytes allows manipultion and display as kb, mb, gb, tb, pb
131
- # Adds method: attribute_bytes=() and attribute_bytes(:unit, :option=>value )
132
- def attr_bytes(attribute, *args)
133
- name = "#{self.name}##{attribute}"
134
- opt = EasyAttributes.pop_options(args)
135
- #getter, setter = EasyAttributes.getter_setter(attribute)
136
- attr_accessor attribute if EasyAttributes::Config.orm == :attr
137
- code = %Q(
138
- def #{attribute}_bytes(*args)
139
- args.size == 0 ? v : EasyAttributes::format_bytes(self.#{attribute}, *args)
140
- end
141
- def #{attribute}_bytes=(v)
142
- self.#{attribute} = EasyAttributes.parse_bytes(v)
175
+
176
+ # Public: Defines an attribute as an enumerated set of symbol/values
177
+ #
178
+ # args - list of symbols, reset values, with an optional options Hash
179
+ # a non-symbolic arg resets the counter to that value
180
+ # Non-integer values can be given if the #next() method is provided
181
+ # nil can be passed to skip the positional value
182
+ #
183
+ # Examples
184
+ #
185
+ # definition.define_enum(:active, :inactive)
186
+ # definition.define_enum(:active, :inactive, start:1, step:10)
187
+ # definition.define_enum(:active, 11, :retired, nil, :inactive)
188
+ #
189
+ def define_enum(args, opt={})
190
+ opt = {step:1}.merge(opt)
191
+ opt[:start] ||= self.values.keys.max ? self.values.keys.max + opt[:step] : Config.enum_start
192
+ hash = {}
193
+ i = opt[:start]
194
+ args.flatten.each do |arg|
195
+ if arg.is_a?(Symbol) || arg.nil?
196
+ hash[arg] = i unless arg.nil?
197
+ opt[:step].times {i = i.next}
198
+ else
199
+ i = arg
143
200
  end
144
- )
145
- #puts code
146
- class_eval code
147
- end
148
-
149
- # Creates an money instance method for the given method, named "#{attribute}_money" which returns
150
- # a formatted money string, and a #{attribute}_money= method used to set an edited money string.
151
- # The original method stores the value as integer (cents, or other precision/currency setting). Options:
152
- # * :money_method - Use this as the alternative name to the money-access methods
153
- # * :units - Use this as an alternative suffix name to the money methods ('dollars' gives 'xx_dollars')
154
- # * :precision - The number of digits implied after the decimal, default is 2
155
- # * :separator - The character to use after the integer part, default is '.'
156
- # * :delimiter - The character to use between every 3 digits of the integer part, default none
157
- # * :positive - The sprintf format to use for positive numbers, default is based on precision
158
- # * :negative - The sprintf format to use for negative numbers, default is same as :positive
159
- # * :zero - The sprintf format to use for zero, default is same as :positive
160
- # * :nil - The sprintf format to use for nil values, default none
161
- # * :unit - Prepend this to the front of the money value, say '$', default none
162
- # * :blank - Return this value when the money string is empty or has no digits on assignment
163
- # * :negative_regex - A Regular Expression used to determine if a number is negative (and without a - sign)
164
- #
165
- def attr_money(attribute, *args)
166
- opt = args.last.is_a?(Hash) ? args.pop : {}
167
- money_method = opt.delete(:money_method) || "#{attribute}_#{opt.delete(:units)||'money'}"
201
+ end
202
+ define(hash)
203
+ end
204
+
205
+ # Public: Returns the defined value for the given symbol, or returns
206
+ # the supplied default, nil, or a value yeilded by a block
207
+ #
208
+ # symbol - symbolic name of value, eg. :active
209
+ #
210
+ # Examples
211
+ #
212
+ # definition.value_of(:active) # => 1
213
+ #
214
+ # Returns the defined value of symbol for the attribute
215
+ def value_of(sym, default=nil)
216
+ self.symbols.fetch(sym.to_sym) { block_given? ? yield(sym) : default }
217
+ end
168
218
 
169
- class_eval %Q(
170
- def #{money_method}(*args)
171
- opt = args.last.is_a?(Hash) ? args.pop : {}
172
- EasyAttributes.integer_to_money( #{attribute}, #{opt.inspect}.merge(opt))
219
+ # Public: Returns the defined symbol for the given value, or returns
220
+ # the supplied default, nil, or a value yeilded by a block
221
+ #
222
+ # value - raw value of attribute, eg. 1
223
+ #
224
+ # Examples
225
+ #
226
+ # definition.symbol_of(1) # => :active
227
+ #
228
+ # Returns the defined symbol (eg.:active) for the given value on the attribute
229
+ def symbol_of(value, default=nil)
230
+ self.values.fetch(value) { block_given? ? yield(value) : default }
231
+ end
232
+
233
+ # Public: Returns true if the current value of the attribute is (or is in the list of
234
+ # values) referenced by their symbolic names
235
+ #
236
+ # value - The value to match
237
+ # symbols - array of symbolic value names, eg. :active, :inactive
238
+ #
239
+ # Examples
240
+ #
241
+ # definition.value_in(self.status, :active) # => false (maybe)
242
+ # definition.value_in(self.:status, :active, :inactive) # => true (maybe)
243
+ # self.value_in(:status, :between, :active, :inactive) # => true (maybe)
244
+ #
245
+ # Returns true if the value matches
246
+ def value_in(value, *args)
247
+ args.each do |arg|
248
+ return true if value == value_of(arg)
173
249
  end
250
+ false
251
+ end
252
+
253
+ # Public: Implements the comparison operator (cmp, <=>) for a value against a symbolic name
254
+ #
255
+ # Examples
256
+ #
257
+ # definition.cmp(1,:active) # => -1. 0, or 1 according to the <=> op defined on the value class
258
+ #
259
+ # Returns -1 if value < symbol, 0 if value == symbol, or 1 of value < symbol
260
+ def cmp(value, symbol)
261
+ other = value_of(symbol)
262
+ value <=> other
263
+ end
174
264
 
175
- def #{money_method}=(v, *args)
176
- opt = args.last.is_a?(Hash) ? args.pop : {}
177
- self.#{attribute} = EasyAttributes.money_to_integer( v, #{opt.inspect}.merge(opt))
265
+ # Public: Compares the value to be between the representation of of the two symbolic names
266
+ # Returns true if value withing the designated range, false otherwise
267
+ def between(value, op, symbol1, symbol2=nil)
268
+ v1 = value_of(symbol1)
269
+ v2 = value_of(symbol2)
270
+ value >= v1 && value <= v2
271
+ end
272
+
273
+ # Public: Returns the next value in the definition from the given value
274
+ def next(value, default=nil)
275
+ self.values.keys.sort.each {|i| return i if i > value }
276
+ default
277
+ end
278
+ alias :succ :next
279
+
280
+ # Public: Returns the next value in the definition from the given value
281
+ def previous(value, default=nil)
282
+ self.values.keys.sort.reverse.each {|i| return i if i < value }
283
+ default
284
+ end
285
+
286
+ # Public: Builds a list of [option_name, value] pairs useful for HTML select options
287
+ # Where option_name is the first found of:
288
+ # - attr_options[:option_name]
289
+ # - attr_options[:title]
290
+ # - attr_options[:name]
291
+ # - capitalized attribute name
292
+ #
293
+ # attribute - symbolic name of attribute
294
+ #
295
+ # Returns an array of [option_name, value] pairs.
296
+ def select_option_values(*args)
297
+ self.symbols.collect {|s,v| [symbol_option_name(s,*args), v]}
298
+ end
299
+
300
+ # Private: Builds a list of [option_name, symbol] pairs useful for HTML select options
301
+ # Where option_name is as defined in selection_option_values
302
+ #
303
+ # attribute - symbolic name of attribute
304
+ #
305
+ # Returns an array of [option_name, symbol] pairs.
306
+ def select_option_symbols(*args)
307
+ self.symbols.collect {|s,v| [symbol_option_name(s,*args), s]}
308
+ end
309
+
310
+ def symbol_option_name(s, *args)
311
+ if @attr_options.has_key?(s)
312
+ args.each {|arg| return @attr_options[s][arg] if @attr_options[s].has_key?(arg) }
313
+ [:option_name, :title, :name] .each do |f|
314
+ return @attr_options[s][f] if @attr_options[s].has_key?(f)
315
+ end
178
316
  end
179
- )
317
+ s.to_s.capitalize.gsub(/_/, ' ')
180
318
  end
181
-
182
- end
183
319
 
184
- # Returns a [getter_code, setter_code] depending on the orm configuration
185
- def self.getter_setter(attribute)
186
- if EasyAttributes::Config.orm == :active_model
187
- ["self.attributes[:#{attribute}]", "self.write_attribute(:#{attribute}, v)"]
188
- else
189
- ["@#{attribute}", "@#{attribute} = v"]
320
+ # Defines each() for Enumerable
321
+ def each
322
+ @definition.symbols.each {|s,v| yield(s,v)}
190
323
  end
191
- end
192
324
 
193
- def self.pop_options(args, defaults={})
194
- args.last.is_a?(Hash) ? defaults.merge(args.pop) : defaults
195
- end
196
-
197
- def self.add_definition(attribute, hash, opt={})
198
- EasyAttributes::Config.define attribute, hash
199
- end
200
-
201
- ##############################################################################
202
- # attr_values helpers
203
- ##############################################################################
204
-
205
- # Returns the defined value for the given symbol and attribute
206
- def self.value_for_sym(attribute, sym)
207
- EasyAttributes::Config.attributes[attribute][sym] or
208
- raise "EasyAttributes #{attribute} value :#{sym} not declared"
209
- end
210
-
211
- # Returns the defined symbol for the given value on the attribute
212
- def self.sym_for_value(attribute, value)
213
- return nil if value.nil?
214
- EasyAttributes::Config.attributes[attribute].each {|k,v| return k if v==value}
215
- raise "EasyAttribute #{attribute} symbol not found for #{value}"
216
- end
217
-
218
- def self.value_is?(attribute, value, *args)
219
- case args.first
220
- when :between then
221
- value >= EasyAttributes::Config.attributes[attribute][args[1]] && value <= EasyAttributes::Config.attributes[attribute][args[2]]
222
- when :gt, :greater_than then
223
- value > EasyAttributes::Config.attributes[attribute][args[1]]
224
- when :ge, :greater_than_or_equal_to then
225
- value >= EasyAttributes::Config.attributes[attribute][args[1]]
226
- when :lt, :less_than then
227
- value < EasyAttributes::Config.attributes[attribute][args[1]]
228
- when :le, :less_than_or_equal_to then
229
- value <= EasyAttributes::Config.attributes[attribute][args[1]]
230
- #when :not, :not_in
231
- # ! args.include? EasyAttributes::Config.attributes[attribute].keys
232
- else
233
- args.include? EasyAttributes.sym_for_value(attribute, value)
325
+ # Defines <=>() for Enumberable comparisons
326
+ def <=>(other)
327
+ @definition.cmp(@value,other)
234
328
  end
235
- end
236
-
237
- ##############################################################################
238
- # attr_byte helpers
239
- ##############################################################################
240
-
241
- # Official Definitions for kilobyte and kibibyte quantity prefixes
242
- KB =1000; MB =KB **2; GB= KB **3; TB =KB **4; PB =KB **5; EB =KB **6; ZB =KB **7; YB =KB **8
243
- KiB=1024; MiB=KiB**2; GiB=KiB**3; TiB=KiB**4; PiB=KiB**5; EiB=KiB**6; ZiB=KiB**7; YiB=KiB**8
244
- DECIMAL_PREFIXES = {:B=>1, :KB=>KB, :MB=>MB, :GB=>GB, :TB=>TB, :PB=>PB, :EB=>EB, :ZB=>ZB, :TB=>YB}
245
- BINARY_PREFIXES = {:B=>1, :KiB=>KiB, :MiB=>MiB, :GiB=>GiB, :TiB=>TiB, :PiB=>PiB, :EiB=>EiB, :ZiB=>ZiB, :TiB=>YiB}
246
-
247
- # Returns a hash of prefix names to decimal quantities for the given setting
248
- def self.byte_prefixes(kb_size=0)
249
- case kb_size
250
- when 1000, :decimal, :si then DECIMAL_PREFIXES
251
- when :old, :jedec, 1024 then {:KB=>KiB, :MB=>MiB, :GB=>GiB, :TB=>TiB, :PB=>PiB, :EB=>EiB, :ZB=>ZiB, :TB=>YiB}
252
- when :new, :iec then BINARY_PREFIXES
253
- else DECIMAL_PREFIXES.merge(BINARY_PREFIXES) # Both? What's the least surprise?
329
+
330
+ # For the experimental Value class. Takes a value or symbol
331
+ def value(v)
332
+ Value.new(self, v)
254
333
  end
255
- end
256
-
257
- # Takes a number of bytes and an optional :unit argument and :precision option, returns a formatted string of units
258
- def self.format_bytes(v, *args)
259
- opt = EasyAttributes.pop_options(args, :precision=>3)
260
- prefixes = EasyAttributes.byte_prefixes(opt[:kb_size]||EasyAttributes::Config.kb_size||0)
261
- if args.size > 0 && args.first.is_a?(Symbol)
262
- (unit, precision) = args
263
- v = "%.#{precision||opt[:precision]}f" % (1.0 * v / (prefixes[unit]||1))
264
- return "#{v} #{unit}"
265
- else
266
- precision = args.shift || opt[:precision]
267
- prefixes.sort{|a,b| a[1]<=>b[1]}.reverse.each do |pv|
334
+
335
+ def inspect
336
+ @value
337
+ end
338
+
339
+ ###########################################################################
340
+ # Official Definitions for kilobyte and kibibyte quantity units
341
+ ###########################################################################
342
+ KB =1000; MB=KB ** 2; GB=KB ** 3; TB=KB ** 4; PB=KB ** 5; EB=KB ** 6; ZB=KB ** 7; YB=KB ** 8
343
+ KiB=1024; MiB=KiB**2; GiB=KiB**3; TiB=KiB**4; PiB=KiB**5; EiB=KiB**6; ZiB=KiB**7; YiB=KiB**8
344
+ BINARY_UNITS = {:B=>1,:KiB=>KiB,:MiB=>MiB,:GiB=>GiB,:TiB=>TiB,:PiB=>PiB,:EiB=>EiB,:ZiB=>ZiB,:YiB=>YiB}
345
+ DECIMAL_UNITS = {:B=>1,:KB=>KB, :MB=>MB, :GB=>GB, :TB=>TB, :PB=>PB, :EB=>EB, :ZB=>ZB, :YB=>YB}
346
+ JEDEC_UNITS = {:B=>1,:KB=>KiB, :MB=>MiB, :GB=>GiB, :TB=>TiB, :PB=>PiB, :EB=>EiB, :ZB=>ZiB, :YB=>YiB}
347
+
348
+ # Public: Maps the kb_size into a hash of desired unit_symbol=>bytes.
349
+ #
350
+ # kb_size - For decimal units: 1000, :decimal, :si, :kb
351
+ # For binary units : 1024, :jedec, :old, :kib
352
+ # Otherwise a hash of combined values is returned
353
+ #
354
+ # Returns a hash of prefix names to decimal quantities for the given setting
355
+ def self.byte_units(kb_size=0)
356
+ case kb_size
357
+ when 1000, :decimal, :si, :kb, :KB then DECIMAL_UNITS
358
+ when :old, :jedec, 1024, :kib, :KiB then JEDEC_UNITS
359
+ when :new, :iec then BINARY_UNITS
360
+ else DECIMAL_UNITS.merge(BINARY_UNITS) # Both? What's the least surprise?
361
+ end
362
+ end
363
+
364
+ # Private: Formats an integer as a byte-representation
365
+ #
366
+ # v - Integer value of bytes
367
+ # unit - Optional Unit to use for representation, regardless of magnitude
368
+ # opt - Optional hash of overrides for :kb_size, :precision, etc.
369
+ #
370
+ # Example:
371
+ # format_bytes(1000, :kb) # => "1 KB"
372
+ #
373
+ # Returns a string like "n.nn XB" representing the approximate bytes
374
+ #
375
+ def format_bytes(v, *args)
376
+ return v if v.nil?
377
+ opt = args.last.is_a?(Hash) ? args.pop : {}
378
+ opt = attr_options.merge(opt)
379
+ unit = args.shift
380
+ units = Definition.byte_units(opt[:kb_size]||Config.kb_size||1000)
381
+ precision = opt[:precision] || attr_options[:precision] || 0
382
+
383
+ if unit
384
+ units = Definition.byte_units() unless units.has_key?(unit)
385
+ v = "%.#{precision}f" % (1.0 * v / (units[unit]||1))
386
+ return "#{v} #{unit}"
387
+ end
388
+
389
+ units.sort{|a,b| a[1]<=>b[1]}.reverse.each do |pv|
268
390
  next if pv[1] > v
269
- v = "%f.10f" % (1.0 * v / pv[1])
270
- v = v[0,precision+1] if v =~ /^(\d)+\.(\d+)/ && v.size > (precision+1)
391
+ v = "%.#{precision}f" % (1.0 * v / pv[1])
271
392
  v.gsub!(/\.0*$/, '')
272
393
  return "#{v} #{pv[0]}"
273
394
  end
395
+ v.to_s
396
+ end
397
+
398
+ # Private: Parses a "1.23 kb" style string and converts into an integer
399
+ # v - String to parse of the format "1.23 kb" or so
400
+ # Optionally, this can be an array of [1.23, :kb]
401
+ # options - Hash of options to override defaults
402
+ # kb_size:1000
403
+ #
404
+ # Returns an integer of the parsed value.
405
+ def parse_bytes(v, *args)
406
+ opt = args.last.is_a?(Hash) ? args.pop : {}
407
+ opt = attr_options.merge(opt)
408
+ # Handle v= [100, :KB]
409
+ if v.is_a?(Array)
410
+ bytes = v.shift
411
+ v = "#{bytes} #{v.shift}"
412
+ else
413
+ bytes = v.to_f
414
+ end
415
+
416
+ if v.downcase =~ /^\s*(?:[\d\.]+)\s*([kmgtpezy]i?b)/i
417
+ unit = ($1.size==2 ? $1.upcase : $1[0,1].upcase+$1[1,1]+$1[2,1].upcase).to_sym
418
+ units = Definition.byte_units(opt[:kb_size]||Config.kb_size||1000)
419
+ units = Definition.byte_units(:both) unless units.has_key?(unit)
420
+ bytes *= units[unit] if units.has_key?(unit)
421
+ end
422
+ (bytes*100 + 0.00001).to_i/100
274
423
  end
275
- v.to_s
276
424
  end
277
-
278
- # Takes a string of "number units" or Array of [number, :units] and returns the number of bytes represented.
279
- def self.parse_bytes(v, *args)
280
- opt = EasyAttributes.pop_options(args, :precision=>3)
281
- # Handle v= [100, :KB]
282
- if v.is_a?(Array)
283
- bytes = v.shift
284
- v = "#{bytes} #{v.shift}"
285
- else
286
- bytes = v.to_f
287
- end
288
-
289
- if v.downcase =~ /^\s*(?:[\d\.]+)\s*([kmgtpezy]i?b)/i
290
- units = ($1.size==2 ? $1.upcase : $1[0,1].upcase+$1[1,1]+$1[2,1].upcase).to_sym
291
- prefixes = EasyAttributes.byte_prefixes(opt[:kb_size]||EasyAttributes::Config.kb_size||0)
292
- bytes *= prefixes[units] if prefixes.has_key?(units)
293
- #puts "v=#{v} b=#{bytes} u=#{units} #{units.class} bp=#{prefixes[units]} kb=#{opt[:kb_size]} P=#{prefixes.inspect}"
294
- end
295
- (bytes*100).to_i/100
425
+
426
+ #############################################################################
427
+ # EasyAttributes::Value - Experiment! Value Class for attribute values
428
+ #############################################################################
429
+ class Value
430
+ include Comparable
431
+
432
+ # Usage: Value.new(definition, symbol_or_value)
433
+ def initialize(definition, value)
434
+ @definition = definition
435
+ @attribute = @definition.attribute
436
+ if value.is_a?(Symbol)
437
+ @value = @definition.value_of(value)
438
+ else
439
+ @value = value
440
+ end
441
+ end
442
+
443
+ # Returns the symbolic name of this value
444
+ def to_sym
445
+ @definition.symbol_of(@value)
446
+ end
447
+
448
+ # Compare with a defined symbol or another value. Required for Comparable
449
+ def <=>(other)
450
+ if other.is_a?(Symbol)
451
+ @value <=> @definition.value_of(other)
452
+ else
453
+ @value <=> other
454
+ end
455
+ end
456
+
457
+ # Returns the next value in the definition as a Value object
458
+ def next
459
+ Value.new(@definition.next(@value))
460
+ end
461
+ alias :succ :next
462
+
463
+ # Forward all other methods to the actual @value class
464
+ def method_missing(method, *args)
465
+ @value.send(method, *args)
466
+ end
296
467
  end
297
-
298
- ##############################################################################
299
- # attr_money helpers
300
- ##############################################################################
301
-
302
- # Returns the money string of the given integer value. Uses relevant options from #easy_money
303
- def self.integer_to_money(value, *args)
304
- opt = args.last.is_a?(Hash) ? args.pop : {}
305
- opt[:positive] ||= "%.#{opt[:precision]||2}f"
306
- pattern =
468
+
469
+ #############################################################################
470
+ # EasyAttributes::Config - Namespace to define and hold configuration data.
471
+ #############################################################################
472
+ class Config
473
+ @orm = :attr # :attr, :active_model
474
+ @kb_size = :iec # :iec, :old, :new
475
+
476
+ # Public: Set the default size for a kilobyte/kibibyte
477
+ # Refer to: http://en.wikipedia.org/wiki/Binary_prefix
478
+ #
479
+ # setting - How to represent kilobyte
480
+ # :new, :iec uses KiB=1024, no KB
481
+ # :old, :jedec, or 1024 uses KB=1024
482
+ # 1000, :decimal uses only KB (1000) (other values mix KB and KiB units)
483
+ # Note: "IEC" is the International Electrotechnical Commission
484
+ # "JEDEC" is the Joint Electron Devices Engineering Council
485
+ #
486
+ # Examples
487
+ #
488
+ # EasyAttributes::Config.kb_size = :iec
489
+ #
490
+ # Returns new setting
491
+ #
492
+ def self.kb_size=(setting)
493
+ @kb_size = setting
494
+ end
495
+
496
+ # Public: Returns the current kb_size setting for use in computing Bytes
497
+ #
498
+ # Returns the current kb_size setting
499
+ #
500
+ def self.kb_size
501
+ case @kb_size
502
+ when :new, :iec then
503
+ 1024
504
+ when :old, :jedec, :decimal
505
+ 1000
506
+ else
507
+ @kb_size
508
+ end
509
+ end
510
+
511
+ # Public: Sets the ORM (Object Relational Mapper) to a supported policy
512
+ #
513
+ # new_orm - value
514
+ # :attr = attr_accessor style operations
515
+ # :acitve_model = Rails ActiveModel operations
516
+ #
517
+ # Returns the new ORM setting
518
+ #
519
+ def self.orm=(new_orm)
520
+ @orm = new_orm
521
+ end
522
+
523
+ # Public: Returns the current ORM setting
524
+ #
525
+ # Returns the current ORM setting
526
+ #
527
+ def self.orm
528
+ @orm
529
+ end
530
+
531
+ # Directive to create constants from field value names: FIELD_NAME_VALUE_NAME=value
532
+ def self.constantize=(b)
533
+ @constantize = b ? true : false
534
+ end
535
+
536
+ def self.constantize
537
+ @constantize || false
538
+ end
539
+
540
+ # Starting point for enum sequences (usually 0 or 1)
541
+ def self.enum_start
542
+ @enum_start || 1
543
+ end
544
+
545
+ def self.enum_start=(n)
546
+ @enum_start = n
547
+ end
548
+ end
549
+
550
+ # Private Module: FixedPoint handles integer<->fixed-point numbers
551
+ # Fixed-Point rules are a hash of these values
552
+ # :units Use this as an alternative suffix name to the money methods ('dollars' gives 'xx_dollars')
553
+ # :precision The number of digits implied after the decimal, default is 2
554
+ # :separator The character to use after the integer part, default is '.'
555
+ # :delimiter The character to use between every 3 digits of the integer part, default none
556
+ # :positive The sprintf format to use for positive numbers, default is based on precision
557
+ # :negative The sprintf format to use for negative numbers, default is same as :positive
558
+ # :zero The sprintf format to use for zero, default is same as :positive
559
+ # :nil The sprintf format to use for nil values, default none
560
+ # :unit Prepend this to the front of the money value, say '$', default none
561
+ # :blank Return this value when the money string is empty or has no digits on assignment
562
+ # :negative_regex A Regular Expression used to determine if a number is negative (and without a - sign)
563
+ module FixedPoint
564
+ ###########################################################################
565
+ # EasyAttributes Fixed-Precision as Integer Helpers
566
+ ###########################################################################
567
+
568
+ # Private: Formats an integer value as the defined fixed-point representation
569
+ # value - integer representation of number
570
+ # rules - hash of fixed-point conversion rules
571
+ #
572
+ # Returns the fixed-point representation as a string for editing
573
+ def self.integer_to_fixed_point(value, *args)
574
+ opt = args.last.is_a?(Hash) ? args.pop : {}
575
+ opt[:positive] ||= "%.#{opt[:precision]||2}f"
576
+ pattern =
307
577
  if value.nil?
308
578
  value = 0
309
579
  opt[:nil] || opt[:positive]
@@ -311,72 +581,464 @@ module EasyAttributes
311
581
  case value <=> 0
312
582
  when 1 then opt[:positive]
313
583
  when 0 then opt[:zero] || opt[:positive]
314
- else
584
+ else
315
585
  value = -value if opt[:negative] && opt[:negative] != opt[:positive]
316
586
  opt[:negative] || opt[:positive]
317
587
  end
318
588
  end
319
- value = self.format_money( value, pattern, opt)
320
- value = opt[:unit]+value if opt[:unit]
321
- value.gsub!(/\./,opt[:separator]) if opt[:separator]
322
- if opt[:delimiter] && (m = value.match(/^(\D*)(\d+)(.*)/))
323
- # Adapted From Rails' ActionView::Helpers::NumberHelper
324
- n = m[2].gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{opt[:delimiter]}")
325
- value=m[1]+n+m[3]
326
- end
327
- value
328
- end
589
+ value = self.format_fixed_point( value, pattern, opt)
590
+ value = opt[:unit]+value if opt[:unit]
591
+ value.gsub!(/\./,opt[:separator]) if opt[:separator]
592
+ if opt[:delimiter] && (m = value.match(/^(\D*)(\d+)(.*)/))
593
+ # Adapted From Rails' ActionView::Helpers::NumberHelper
594
+ n = m[2].gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{opt[:delimiter]}")
595
+ value=m[1]+n+m[3]
596
+ end
597
+ value
598
+ end
329
599
 
330
- def self.integer_to_float(value, *args)
331
- opt = args.last.is_a?(Hash) ? args.pop : {}
332
- return (opt[:blank]||nil) if value.nil?
333
- value = 1.0 * value / (10**(opt[:precision]||2))
334
- end
600
+ # Private: Converts the integer into a float value with the given fixed-point definition
601
+ #
602
+ # value - integer to convert
603
+ # rules - hash of fixed-point conversion rules
604
+ #
605
+ # Returns a float of the converted value
606
+ def self.integer_to_float(value, *args)
607
+ opt = args.last.is_a?(Hash) ? args.pop : {}
608
+ return (opt[:blank]||nil) if value.nil?
609
+ value = 1.0 * value / (10**(opt[:precision]||2))
610
+ end
335
611
 
336
- # Returns the integer of the given money string. Uses relevant options from #easy_money
337
- def self.money_to_integer(value, *args)
338
- opt = args.last.is_a?(Hash) ? args.pop : {}
339
- value = value.gsub(opt[:delimiter],'') if opt[:delimiter]
340
- value = value.gsub(opt[:separator],'.') if opt[:separator]
341
- value = value.gsub(/^[^\d\.\-\,]+/,'')
342
- return (opt[:blank]||nil) unless value =~ /\d/
343
- m = value.to_s.match(opt[:negative_regex]||/^(-?)(.+\d)\s*cr/i)
344
- value = value.match(/^-/) ? m[2] : "-#{m[2]}" if m && m[2]
345
-
346
- # Money string ("123.45") to proper integer withough passing through the float transformation
347
- match = value.match(/(-?\d*)\.?(\d*)/)
348
- return 0 unless match
349
- value = match[1].to_i * (10 ** (opt[:precision]||2))
350
- cents = match[2]
351
- cents = cents + '0' while cents.length < (opt[:precision]||2)
352
- cents = cents.to_s[0,opt[:precision]||2]
353
- value += cents.to_i * (value<0 ? -1 : 1)
354
- value
355
- end
612
+ # Private: Takes a string of a fixed-point representation (from editing) and converts it to
613
+ # the integer representation according to the passed rules hash
614
+ # rules - hash of fixed-point conversion rules
615
+ #
616
+ # Returns the integer of the given money string. Uses relevant options from #easy_money
617
+ def self.fixed_point_to_integer(value, *args)
618
+ opt = args.last.is_a?(Hash) ? args.pop : {}
619
+ value = value.to_s
620
+ value = value.gsub(opt[:delimiter],'') if opt[:delimiter]
621
+ value = value.gsub(opt[:separator],'.') if opt[:separator]
622
+ value = value.gsub(/^[^\d\.\-\,]+/,'')
623
+ return (opt[:blank]||nil) unless value =~ /\d/
624
+ m = value.to_s.match(opt[:negative_regex]||/^(-?)(.+\d)\s*cr/i)
625
+ value = value.match(/^-/) ? m[2] : "-#{m[2]}" if m && m[2]
356
626
 
357
- # Returns the integer (cents) value from a Float
358
- def self.float_to_integer(value, *args)
359
- opt = args.last.is_a?(Hash) ? args.pop : {}
360
- return (opt[:blank]||nil) if value.nil?
361
- value = (value.to_f*(10**((opt[:precision]||2)+1))).to_i/10 # helps rounding 4.56 -> 455 ouch!
362
- end
363
-
364
- # Replacing the sprintf function to deal with money as float. "... %[flags]m ..."
365
- def self.format_money(value, pattern="%.2m", *args)
366
- opt = args.last.is_a?(Hash) ? args.pop : {}
367
- sign = value < 0 ? -1 : 1
368
- dollars, cents = value.abs.divmod( 10 ** (opt[:precision]||2))
369
- dollars *= sign
370
- parts = pattern.match(/^(.*)%([-\. \d+0]*)[fm](.*)/)
371
- return pattern unless parts
372
- intdec = parts[2].match(/(.*)\.(\d*)/)
373
- dprec, cprec = intdec ? [intdec[1], intdec[2]] : ['', '']
374
- dollars = sprintf("%#{dprec}d", dollars)
375
- cents = '0' + cents.to_s while cents.to_s.length < (opt[:precision]||2)
376
- cents = cents.to_s[0,cprec.to_i]
377
- cents = cents + '0' while cents.length < cprec.to_i
378
- value = parts[1] + "#{(dollars.to_i==0 && sign==-1) ? '-' : '' }#{dollars}#{cents>' '? '.':''}#{cents}" + parts[3]
379
- value
380
- end
381
-
382
- end
627
+ # Money string ("123.45") to proper integer withough passing through the float transformation
628
+ match = value.match(/(-?\d*)\.?(\d*)/)
629
+ return 0 unless match
630
+ value = match[1].to_i * (10 ** (opt[:precision]||2))
631
+ cents = match[2]
632
+ cents = cents + '0' while cents.length < (opt[:precision]||2)
633
+ cents = cents.to_s[0,opt[:precision]||2]
634
+ value += cents.to_i * (value<0 ? -1 : 1)
635
+ value
636
+ end
637
+
638
+ # Returns the integer (cents) value from a Float
639
+ # rules - hash of fixed-point conversion rules
640
+ def self.float_to_integer(value, *args)
641
+ opt = args.last.is_a?(Hash) ? args.pop : {}
642
+ return (opt[:blank]||nil) if value.nil?
643
+ value = (value.to_f*(10**((opt[:precision]||2)+1))).to_i/10 # helps rounding 4.56 -> 455 ouch!
644
+ end
645
+
646
+ # Replacing the sprintf function to deal with money as float. "... %[flags]m ..."
647
+ # rules - hash of fixed-point conversion rules
648
+ def self.format_fixed_point(value, pattern="%.2m", *args)
649
+ opt = args.last.is_a?(Hash) ? args.pop : {}
650
+ sign = value < 0 ? -1 : 1
651
+ dollars, cents = value.abs.divmod( 10 ** (opt[:precision]||2))
652
+ dollars *= sign
653
+ parts = pattern.match(/^(.*)%([-\. \d+]*)[fm](.*)/)
654
+ return pattern unless parts
655
+ intdec = parts[2].match(/(.*)\.(\d*)/)
656
+ dprec, cprec = intdec ? [intdec[1], intdec[2]] : ['', '']
657
+ dollars = sprintf("%#{dprec}d", dollars)
658
+ cents = '0' + cents.to_s while cents.to_s.length < (opt[:precision]||2)
659
+ cents = cents.to_s[0,cprec.to_i]
660
+ cents = cents + '0' while cents.length < cprec.to_i
661
+ value = parts[1] + "#{(dollars.to_i==0 && sign==-1) ? '-' : '' }#{dollars}#{cents>' '? '.':''}#{cents}" + parts[3]
662
+ value
663
+ end
664
+ end # FixedPoint
665
+
666
+
667
+ #############################################################################
668
+ # EasyAttributes::ClassMethods - Module defining methods added to classes when included
669
+ #
670
+ # Examples
671
+ # class MyClass
672
+ # include EasyAttributes
673
+ # attr_values :attr, name:value, ...
674
+ # attr_enum :attr, :name, ....
675
+ # attr_define :attr, :attr=>:defined_attr
676
+ # attr_bytes :attr, ..., :base=>2
677
+ # attr_money :attr, :precision=>2
678
+ # attr_fixed :attr, :precision=>2
679
+ #############################################################################
680
+ module ClassMethods
681
+
682
+ # Public: Defines an attribute with a Hash of symbolic synonyms for the values.
683
+ #
684
+ # attribute - symbolic name of the attribute
685
+ # values - a hash of {symbol:value, ....} mappings
686
+ # a optional key of :options=>{name:value} defines any options for the attribute
687
+ # Examples
688
+ #
689
+ # attr_values :status, active:1, inactive:2
690
+ #
691
+ # Creates these instance methods (for a "status" attribute):
692
+ #
693
+ # status_sym() # Returns the symbolic name instead of value
694
+ # status_sym=(:inactive) # Used for setting the attrivute by symbolic name instead
695
+ # status_in(symbol, ...) # Returns true if the attribute symbol is in the list of symbols
696
+ # status_cmp(symbol) # Returns the comparison of the value <=> symbol
697
+ #
698
+ # And these class methods:
699
+ #
700
+ # status_definition()
701
+ # Returns the EasyAttributes::Definition for the attribute, on which you can call cool things like
702
+ # value_of(), symbol_of(), select_options(), etc.
703
+ #
704
+ # Returns nothing
705
+ def attr_values(attribute, *args)
706
+ #defn = Definition.find_or_create(attribute, *args)
707
+ defn = Definition.new(attribute, *args)
708
+ easy_attribute_accessors(attribute, defn)
709
+ end
710
+
711
+ # Public: Defines an attribute as an Enumeration of symbol name.
712
+ #
713
+ # By default, the first symbol is mapped to 0 (zero), and increments by 1. The :start and
714
+ # :step values in the options hash can change those settings. Also, "#next()" is called on
715
+ # the start value, so any non-numeric object that supports that call can be used (such as
716
+ # a string). A nil in a position will skip that value, and any other non-symbol will reset
717
+ # the value of the next symbol to that one.
718
+ #
719
+ # This is an alternate syntax for the attr_values() method
720
+ #
721
+ # attribute - symbolic name of the attribute
722
+ # args - a list of symbol names, nil skip tokens, and value changes
723
+ # * A symbol name will map to the current value
724
+ # * A nil skips the current value in the list
725
+ # * Any other value replaces the current value for the subsequent symbol
726
+ # options - a optional key of :options=>{name:value} defines any options for the attribute
727
+ #
728
+ # Examples
729
+ #
730
+ # attr_enum :status, :active, :inactive # => {active:0, inactive:1}
731
+ # attr_enum :month, :jan, :feb, ..., :dec, start:1 # => {jan:1, feb:2, ..., dec:12}
732
+ # attr_enum :status, :active, :inactive, nil, :suspended, 99, :deleted, start:10, step:10
733
+ # # => same as: {active:10, inactive:20, suspended:40, deleted:99}
734
+ #
735
+ # Creates the same methods as attr_values().
736
+ #
737
+ # Returns nothing
738
+ def attr_enum(attribute, *args)
739
+ opt = args.last.is_a?(Hash) ? args.pop : {}
740
+ defn = Definition.new(attribute, args, opt)
741
+ easy_attribute_accessors(attribute, defn)
742
+ end
743
+
744
+ # Public: Defines an attribute as a set of values. For String-type attributes
745
+ #
746
+ # attribute - symbolic name of the attribute
747
+ # args - a list of allowed string names (symbols can be used for convenience as well)
748
+ #
749
+ # Examples
750
+ #
751
+ # attr_allowed :type, "mammal", :bird, :insect
752
+ #
753
+ # Creates the same methods as attr_values().
754
+ #
755
+ # Returns nothing
756
+ def attr_allowed(attribute, *args)
757
+ opt = args.last.is_a?(Hash) ? args.pop : {}
758
+ vals = {}
759
+ args.flatten.map { |v| vals[v.to_sym] = v.to_s }
760
+ defn = Definition.new(attribute, vals, opt)
761
+ easy_attribute_accessors(attribute, defn)
762
+ end
763
+
764
+ # Public: Sets global definitions into the class by attribute name or Hash mapping.
765
+ #
766
+ # attributes - List of attributes to import from Config definitions
767
+ # mappings - Hash of atttribute names to a matching alternate name in the Config definitions
768
+ #
769
+ # Examples (call like attr_values or attr_enum)
770
+ #
771
+ # attr_define :status, :signup, :confirm, :unreachable, :delete
772
+ # attr_define :status, signup:1, confirm:2, uncreachable:3, delete:4
773
+ #
774
+ # Stores the definition in a symbol table for later attr_shared lookup
775
+ def attr_define(attribute, *definition)
776
+ find_or_create(attribute, *definition)
777
+ end
778
+
779
+ # Public: Imports previously defined definitions into the class by attribute name or Hash mapping.
780
+ #
781
+ # attributes - List of attributes to import from Config definitions
782
+ # mappings - Hash of atttribute names to a matching alternate name in the Config definitions
783
+ #
784
+ # Examples
785
+ #
786
+ # attr_shared :status, :role, widget_type: :general_type, colname: :sharedname
787
+ # attr_shared employee_status:Employee.status_definition()
788
+ #
789
+ # Calls attr_values with each definition
790
+ def attr_shared(*attributes)
791
+ mapping = attributes.last.is_a?(Hash) ? attributes.pop : {}
792
+ attributes.each { |a| install_shared_attribute(a) }
793
+ mapping.each { |a, shared| install_shared_attribute(a, shared) }
794
+ end
795
+
796
+ def install_shared_attribute(name, shared_name=nil)
797
+ shared_name ||= name
798
+ defn = Definition.find_or_create(shared_name)
799
+ easy_attribute_accessors(name, defn)
800
+ end
801
+
802
+ # Private: Creates attribute accessors for the attribute /definition for attr_values
803
+ #
804
+ # Creates these methods dynamically on the host class
805
+ # self.<attribute>_definition()
806
+ # <attribute>_sym()
807
+ # <attribute>_sym=()
808
+ # <attribute>_in()
809
+ # <attribute>_cmp()
810
+ #
811
+ # If Config.constantize, create ATTRIBUTE_SYMBOL=value constants
812
+ #
813
+ def easy_attribute_accessors(attribute, defn)
814
+ attribute = attribute.to_sym
815
+ @easy_attribute_definitions ||= {}
816
+ @easy_attribute_definitions[attribute] = defn
817
+ opt = defn.options
818
+ code = ''
819
+
820
+ if (EasyAttributes::Config.orm == :active_model || opt[:orm] == :active_model) &&
821
+ self.respond_to?(:validates_inclusion_of)
822
+ self.validates_inclusion_of attribute, :in=>defn.symbols.values
823
+ # Add named_scope (scope) for each value
824
+ if opt[:named_scope]
825
+ defn.symbols.each { |k,v| code += "named_scope :#{k}, :conditions=>{:#{attribute}=>#{v.inspect}}\n" }
826
+ end
827
+ if opt[:scope]
828
+ defn.symbols.each { |k,v| code += "scope :#{k}, where({:#{attribute}=>#{v.inspect}})\n" }
829
+ end
830
+ else
831
+ attr_accessor attribute
832
+ end
833
+
834
+ #------------------------------------------------------------------------
835
+ # Class Methods
836
+ #------------------------------------------------------------------------
837
+
838
+ define_easy_attribute_definition
839
+
840
+ # <attribute>_options() Returns an array of (HTML Select) option pairs
841
+ # => [["Option Name", :symbol], ...]
842
+ self.define_singleton_method("#{attribute}_options") do |*args|
843
+ easy_attribute_definition(attribute).select_option_symbols(*args)
844
+ end
845
+
846
+ # <attribute>_of(:sym [,:default_sym]) Returns the symbol/value hash for the attribute
847
+ self.define_singleton_method("#{attribute}_of") do |*args, &block|
848
+ easy_attribute_definition(attribute).symbols.fetch(args.first.to_sym) do |sym|
849
+ if args.size>1
850
+ easy_attribute_definition(attribute).symbols.fetch(args[1].to_sym, &block)
851
+ else
852
+ raise "#{attribute} symbolic name #{sym} not found"
853
+ end
854
+ end
855
+ end
856
+
857
+ # Define Constants Model::ATTRIBUTE_SYMBOL = value
858
+ if Config::constantize
859
+ easy_attribute_definition(attribute).symbols.each do |sym, value|
860
+ const_set("#{attribute.upcase}_#{sym.to_s.upcase}", value)
861
+ end
862
+ end
863
+
864
+ #------------------------------------------------------------------------
865
+ # Instance Methods
866
+ #------------------------------------------------------------------------
867
+
868
+ # <attribute>_definition()
869
+ # Returns the definition ojbect for the easy attribute
870
+ define_method("#{attribute}_definition") do
871
+ self.class.easy_attribute_definition(attribute)
872
+ end
873
+
874
+ # <attribute>_sym()
875
+ # Returns the symbolic name of the current value of "attribute"
876
+ define_method("#{attribute}_sym") do
877
+ self.class.easy_attribute_definition(attribute).symbol_of(self.send(attribute))
878
+ end
879
+
880
+ # <attribute>_sym=(new_symbol)
881
+ # Sets the value of "attribute" to the associated value of the passed symbolic name
882
+ define_method("#{attribute}_sym=") do |sym|
883
+ self.send("#{attribute}=", self.class.easy_attribute_definition(attribute).value_of(sym))
884
+ end
885
+
886
+ # <attribute>_in(*names)
887
+ # Returns true if the symbolic name of the current value of "attribute" is in the list of names.
888
+ define_method("#{attribute}_in") do |*args|
889
+ self.class.easy_attribute_definition(attribute).value_in(self.send(attribute),*args)
890
+ end
891
+
892
+ # <attribute>_cmp(other)
893
+ # Standard "cmp" or <=> compare for "attribute" against a symbolic name.
894
+ define_method("#{attribute}_cmp") do |other|
895
+ self.class.easy_attribute_definition(attribute).cmp(self.send(attribute),other)
896
+ end
897
+ end
898
+
899
+ # Adds once to class: Returns the EasyAttribute::Definition of the passed
900
+ def define_easy_attribute_definition
901
+ unless self.respond_to?(:easy_attribute_definition)
902
+ define_singleton_method(:easy_attribute_definition) do |attrib|
903
+ @easy_attribute_definitions.fetch(attrib.to_sym) { raise "EasyAttribute #{attrib} not found" }
904
+ end
905
+ end
906
+ end
907
+
908
+ # Public: Adds byte attributes helpers to the class
909
+ # attr_bytes allows manipultion and display as kb, mb, gb, tb, pb
910
+ # Adds method: attribute_bytes=() and attribute_bytes(:unit, :option=>value )
911
+ #
912
+ # attribites - List of attribute names to generate helpers for
913
+ # options - Hash of byte helper options
914
+ #
915
+ # Example
916
+ #
917
+ # attr_bytes :bandwidth
918
+ # attr_bytes :storage, :kb_size=>1000, :precision=>2
919
+ #
920
+ # Adds the following helpers
921
+ #
922
+ # bandwidth_bytes() # => "10 GB"
923
+ # bandwidth_bytes=("10 GB") # => 10_000_000_000
924
+ #
925
+ def attr_bytes(*args)
926
+ define_easy_attribute_definition
927
+ @easy_attribute_definitions ||= {}
928
+ opt = args.last.is_a?(Hash) ? args.op : {}
929
+
930
+ args.each do |attribute|
931
+ attribute = attribute.to_sym
932
+ unless EasyAttributes::Config.orm == :active_model || opt[:orm] == :active_model
933
+ attr_accessor attribute if EasyAttributes::Config.orm == :attr
934
+ end
935
+ #defn = Definition.find_or_create(attribute, {}, opt)
936
+ defn = Definition.new(attribute, {}, opt)
937
+ @easy_attribute_definitions[attribute] = defn
938
+
939
+ # <attribute>_bytes()
940
+ # Returns the symbolic name of the current value of "attribute"
941
+ define_method("#{attribute}_bytes") do |*bargs|
942
+ self.class.easy_attribute_definition(attribute).format_bytes(self.send(attribute), *bargs)
943
+ end
944
+
945
+ # <attribute>_bytes=(new_symbol)
946
+ # Sets the value of "attribute" to the associated value of the passed symbolic name
947
+ define_method("#{attribute}_bytes=") do |sym|
948
+ self.send("#{attribute}=", self.class.easy_attribute_definition(attribute).parse_bytes(sym))
949
+ end
950
+ end
951
+ end
952
+
953
+ # Public: Adds methods to get and set a fixed-point value stored as an integer.
954
+ #
955
+ # attributes - list of attribute names to define
956
+ # options - Optional hash of definitions for the given list of attributes
957
+ # :method_suffix - Use this as the alternative name to the *_fixed method names
958
+ # :units - Use this as an alternative suffix name to the money methods ('dollars' gives 'xx_dollars')
959
+ # :precision - The number of digits implied after the decimal, default is 2
960
+ # :separator - The character to use after the integer part, default is '.'
961
+ # :delimiter - The character to use between every 3 digits of the integer part, default none
962
+ # :positive - The sprintf format to use for positive numbers, default is based on precision
963
+ # :negative - The sprintf format to use for negative numbers, default is same as :positive
964
+ # :zero - The sprintf format to use for zero, default is same as :positive
965
+ # :nil - The sprintf format to use for nil values, default none
966
+ # :unit - Prepend this to the front of the money value, say '$', default none
967
+ # :blank - Return this value when the money string is empty or has no digits on assignment
968
+ # :negative_regex - A Regular Expression used to determine if a number is negative (and without a - sign)
969
+ #
970
+ # Examples:
971
+ # attr_fixed :gpa, precision:1
972
+ # attr_fixed :price, precision:2
973
+ #
974
+ # Adds the following helpers
975
+ #
976
+ # gpa_fixed() #=> "3.8"
977
+ # gpa_fixed=("3.8") #=> 38
978
+ # gpa_float() #=> 3.8
979
+ #
980
+ # Returns nothing
981
+ def attr_fixed(*args)
982
+ define_easy_attribute_definition
983
+ @easy_attribute_definitions ||= {}
984
+ opt = args.last.is_a?(Hash) ? args.pop : {}
985
+ suffix = opt.fetch(:method_suffix) { 'fixed' }
986
+
987
+ args.each do |attribute|
988
+ attribute = attribute.to_sym
989
+ unless EasyAttributes::Config.orm == :active_model || opt[:orm] == :active_model
990
+ attr_accessor attribute if EasyAttributes::Config.orm == :attr
991
+ end
992
+ defn = Definition.new(attribute, {}, opt)
993
+ @easy_attribute_definitions[attribute] = defn
994
+
995
+ # <attribute>_fixed()
996
+ # Returns the symbolic name of the current value of "attribute"
997
+ define_method("#{attribute}_#{suffix}") do
998
+ FixedPoint::integer_to_fixed_point(send(attribute), self.class.easy_attribute_definition(attribute).attr_options)
999
+ end
1000
+
1001
+ # <attribute>_fixed=(new_value)
1002
+ # Sets the value of "attribute" to the associated value of the passed symbolic name
1003
+ define_method("#{attribute}_#{suffix}=") do |val|
1004
+ self.send("#{attribute}=", FixedPoint::fixed_point_to_integer(val,
1005
+ self.class.easy_attribute_definition(attribute).attr_options))
1006
+ end
1007
+
1008
+ # <attribute>_float()
1009
+ # Returns the value of the attribite as a float with desired precision point
1010
+ define_method("#{attribute}_float") do
1011
+ FixedPoint::integer_to_float(send(attribute), self.class.easy_attribute_definition(attribute).attr_options)
1012
+ end
1013
+ end
1014
+ end
1015
+
1016
+ # Public: Alias of attr_fixed for a money type with suffix of 'money' and precision of 2.
1017
+ #
1018
+ # args - list of money attributes
1019
+ # options - hash of attr_fixed options.
1020
+ #
1021
+ # Examples:
1022
+ # attr_money :price
1023
+ # attr_money :wager, method_suffix:'quatloos', precision:1, unit:'QL'
1024
+ #
1025
+ # Adds the following helpers
1026
+ #
1027
+ # price_money() #=> "42.00"
1028
+ # price_money=("3.8") #=> 380
1029
+ # price_float() #=> 3.8
1030
+ #
1031
+ def attr_money(*args)
1032
+ opt = args.last.is_a?(Hash) ? args.pop : {}
1033
+ opt = {method_suffix:'money', precision:2}.merge(opt)
1034
+ args << opt
1035
+ attr_fixed(*args)
1036
+ end
1037
+
1038
+ def attr_dollars(*args)
1039
+ opt = args.last.is_a?(Hash) ? args.pop : {}
1040
+ attr_money(*args, opt.merge(method_suffix:'dollars'))
1041
+ end
1042
+
1043
+ end # EasyAttributes::ClassMethods
1044
+ end # EasyAttributes Module