easy_attributes 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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