hashdown 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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +95 -0
- data/Rakefile +13 -0
- data/hashdown.gemspec +24 -0
- data/lib/hashdown/cache.rb +51 -0
- data/lib/hashdown/finder.rb +45 -0
- data/lib/hashdown/select_options.rb +56 -0
- data/lib/hashdown/version.rb +3 -0
- data/lib/hashdown.rb +3 -0
- data/spec/lib/hashdown/cache_spec.rb +18 -0
- data/spec/lib/hashdown/finder_spec.rb +71 -0
- data/spec/lib/hashdown/select_options_spec.rb +88 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/models.rb +19 -0
- data/spec/support/schema.rb +11 -0
- data/spec/support/seeds.rb +42 -0
- metadata +132 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Solomon White
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Hashdown
|
2
|
+
|
3
|
+
Hashdown is a super lightweight rails plugin that adds hash-style lookups and
|
4
|
+
option lists (for generating dropdowns) to ActiveRecord models. Note: Hashdown
|
5
|
+
has been updated to support Rails 3. If you're looking for the original plugin
|
6
|
+
version, it's in the [rails 2 branch](https://github.com/rubysolo/hashdown/tree/rails2)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'hashdown'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install hashdown
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Given the following class definition:
|
25
|
+
|
26
|
+
class State < ActiveRecord::Base
|
27
|
+
finder :abbreviation
|
28
|
+
end
|
29
|
+
|
30
|
+
You can look up states via their abbreviations like so:
|
31
|
+
|
32
|
+
@colorado = State[:CO]
|
33
|
+
|
34
|
+
By default, calling the finder method with a token that does not exist in the
|
35
|
+
database will result in an ActiveRecord::RecordNotFount exception being thrown.
|
36
|
+
This can be overridden by adding a :default option to your finder declaration.
|
37
|
+
|
38
|
+
class PurchaseOrder < ActiveRecord::Base
|
39
|
+
finder :number, :default => nil
|
40
|
+
end
|
41
|
+
|
42
|
+
In this case, PurchaseOrder['00734'] will silently return nil if that number is
|
43
|
+
not found.
|
44
|
+
|
45
|
+
These types of reference data models are often something you need to populate a
|
46
|
+
select list on your form, so hashdown includes a method to generate your option
|
47
|
+
list:
|
48
|
+
|
49
|
+
1. Declare your model to be selectable:
|
50
|
+
|
51
|
+
class State < ActiveRecord::Base
|
52
|
+
selectable
|
53
|
+
end
|
54
|
+
|
55
|
+
2. Call select_options in your form to return a set of name, value pairs to
|
56
|
+
pass into a select builder:
|
57
|
+
|
58
|
+
<%= form.select :state_id, State.select_options %>
|
59
|
+
|
60
|
+
By default, selectable will return the :id of the record as the value, and the
|
61
|
+
:name attribute value as the display. This can be overridden inline in the
|
62
|
+
select_options call:
|
63
|
+
|
64
|
+
State.select_options(:name, :abbreviation)
|
65
|
+
|
66
|
+
The grouped_options_for_select format is also supported:
|
67
|
+
|
68
|
+
State.select_options(:group => :region)
|
69
|
+
|
70
|
+
Adding finder and selectable to a model is roughly equivalent to the following
|
71
|
+
implementation:
|
72
|
+
|
73
|
+
class State < ActiveRecord::Base
|
74
|
+
validates_uniqueness_of :abbreviation
|
75
|
+
|
76
|
+
def self.[](state_code)
|
77
|
+
find_by_abbreviation(state_code)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.select_options
|
81
|
+
find(:all).map{|s| [s.name, s.id] }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
...except hashdown adds configuration for flexibility and caching for speedy
|
86
|
+
lookups.
|
87
|
+
|
88
|
+
|
89
|
+
## Contributing
|
90
|
+
|
91
|
+
1. Fork it
|
92
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
93
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
94
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
95
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
desc "Run specs"
|
5
|
+
task :spec do
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
7
|
+
t.rspec_opts = %w{--colour --format progress}
|
8
|
+
t.pattern = 'spec/**/*_spec.rb'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Default: run specs."
|
13
|
+
task :default => :spec
|
data/hashdown.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/hashdown/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Solomon White"]
|
6
|
+
gem.email = ["rubysolo@gmail.com"]
|
7
|
+
gem.description = %q{Hashdown}
|
8
|
+
gem.summary = %q{super lightweight Rails plugin that adds hash-style lookups and option list (for generating dropdowns) to ActiveRecord models}
|
9
|
+
gem.homepage = "https://github.com/rubysolo/hashdown"
|
10
|
+
|
11
|
+
gem.add_dependency 'activerecord', '~> 3.0'
|
12
|
+
|
13
|
+
gem.add_development_dependency 'pry-nav'
|
14
|
+
gem.add_development_dependency 'rake'
|
15
|
+
gem.add_development_dependency 'rspec'
|
16
|
+
gem.add_development_dependency 'sqlite3'
|
17
|
+
|
18
|
+
gem.files = `git ls-files`.split($\)
|
19
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
20
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
21
|
+
gem.name = "hashdown"
|
22
|
+
gem.require_paths = ["lib"]
|
23
|
+
gem.version = Hashdown::VERSION
|
24
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Hashdown
|
2
|
+
def self.cache
|
3
|
+
@cache ||= rails_cache || local_cache
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.cache=(value)
|
7
|
+
@cache = value
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.force_cache_miss?
|
11
|
+
Rails.env.test? if defined?(Rails)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cache_key(source, class_name, token=nil)
|
15
|
+
['hashdown', class_name, source, token].compact.join('-')
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.uncache(source, class_name, token)
|
19
|
+
cache.delete(cache_key(source, class_name, token))
|
20
|
+
end
|
21
|
+
|
22
|
+
class Config
|
23
|
+
def initialize(hash={})
|
24
|
+
@data = hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(method_id, *args)
|
28
|
+
if method_id.to_s =~ /^(\w*)=$/
|
29
|
+
@data[$1.to_sym] = args.first
|
30
|
+
elsif method_id.to_s =~ /^(\w*)\?$/
|
31
|
+
@data.has_key?($1.to_sym)
|
32
|
+
else
|
33
|
+
if @data.has_key?(method_id)
|
34
|
+
@data[method_id]
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def self.rails_cache
|
45
|
+
Rails.cache if defined?(Rails)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.local_cache
|
49
|
+
ActiveSupport::Cache::MemoryStore.new
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'hashdown/cache'
|
2
|
+
|
3
|
+
module Hashdown
|
4
|
+
module Finder
|
5
|
+
def finder(attribute, options={})
|
6
|
+
self.extend ClassMethods
|
7
|
+
self.send(:include, InstanceMethods)
|
8
|
+
|
9
|
+
hashdown.finder = Config.new(options.merge(:key => attribute))
|
10
|
+
|
11
|
+
validates attribute, :uniqueness => true
|
12
|
+
|
13
|
+
after_save :clear_hashdown_finder_cache
|
14
|
+
after_destroy :clear_hashdown_finder_cache
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def hashdown
|
19
|
+
@hashdown ||= Config.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](token)
|
23
|
+
Hashdown.cache.fetch(Hashdown.cache_key(:finder, self.to_s, token), :force => Hashdown.force_cache_miss?) do
|
24
|
+
where({hashdown.finder.key => token.to_s}).first || (
|
25
|
+
hashdown.finder.default? ?
|
26
|
+
hashdown.finder.default :
|
27
|
+
raise(ActiveRecord::RecordNotFound)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module InstanceMethods
|
34
|
+
def hashdown
|
35
|
+
self.class.hashdown
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear_hashdown_finder_cache
|
39
|
+
Hashdown.uncache(:finder, self.class.to_s, self[hashdown.finder.key])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
ActiveRecord::Base.extend Hashdown::Finder
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Hashdown
|
2
|
+
module SelectOptions
|
3
|
+
def select_options(*args)
|
4
|
+
unless self.respond_to?(:map_records_to_select_options)
|
5
|
+
self.extend ClassMethods
|
6
|
+
self.send(:include, InstanceMethods)
|
7
|
+
|
8
|
+
after_save :clear_select_options_cache
|
9
|
+
after_destroy :clear_select_options_cache
|
10
|
+
end
|
11
|
+
|
12
|
+
options = args.pop if args.last.is_a?(Hash)
|
13
|
+
options ||= {}
|
14
|
+
|
15
|
+
options[:label] ||= args.shift || :name
|
16
|
+
options[:value] ||= args.shift || :id
|
17
|
+
|
18
|
+
scope = scoped
|
19
|
+
scope = scope.order(options[:label]) if scope.arel.orders.empty? && columns.any?{|c| c.name == options[:label].to_s }
|
20
|
+
|
21
|
+
Hashdown.cache.fetch(Hashdown.cache_key(:select_options, self.to_s, select_options_cache_key(options, scope)), :force => Hashdown.force_cache_miss?) do
|
22
|
+
if grouping = options[:group]
|
23
|
+
scope.all.group_by {|record| grouping.call(record) }.map do |group, records|
|
24
|
+
[group, map_records_to_select_options(records, options)]
|
25
|
+
end
|
26
|
+
else
|
27
|
+
map_records_to_select_options(scope.all, options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
private
|
34
|
+
|
35
|
+
def select_options_cache_key(options, scope)
|
36
|
+
key = options.sort.each_with_object(""){|ck,(k,v)| ck << "#{ k }:#{ v };" }
|
37
|
+
key << scope.to_sql
|
38
|
+
key = Digest::MD5.hexdigest(key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def map_records_to_select_options(records, options)
|
42
|
+
records.map{|record| [ record[options[:label]], record[options[:value]] ] }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods
|
47
|
+
private
|
48
|
+
|
49
|
+
def clear_select_options_cache
|
50
|
+
Hashdown.cache.delete_matched(Regexp.new(Hashdown.cache_key(:select_options, self.class.to_s)))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
ActiveRecord::Base.extend Hashdown::SelectOptions
|
data/lib/hashdown.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hashdown do
|
4
|
+
describe 'cache' do
|
5
|
+
describe 'in a Rails project' do
|
6
|
+
before { Object.const_set('Rails', mock(cache: :rails_cache)) }
|
7
|
+
after { Object.send(:remove_const, 'Rails') }
|
8
|
+
|
9
|
+
it 'delegates to Rails.cache if available' do
|
10
|
+
Hashdown.cache.should eq Rails.cache
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'creates a new cache store if Rails.cache unavailable' do
|
15
|
+
Hashdown.cache.class.should eq ActiveSupport::Cache::MemoryStore
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hashdown::Finder do
|
4
|
+
describe 'bracket lookup' do
|
5
|
+
it 'is added to enabled models' do
|
6
|
+
State.should respond_to(:[])
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'finds a record by a string key value' do
|
10
|
+
State['CA'].name.should eq 'California'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'finds a record by a symbol key value' do
|
14
|
+
State[:CO].name.should eq 'Colorado'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'adds uniqueness validation to key attribute' do
|
18
|
+
State.where(abbreviation: 'CO').count.should eq 1
|
19
|
+
State.new(abbreviation: 'CO').should_not be_valid
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'missing/invalid key' do
|
24
|
+
it 'raises record not found exception' do
|
25
|
+
lambda { State[:HI] }.should raise_error(ActiveRecord::RecordNotFound)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'allows setting a default to avoid exception' do
|
29
|
+
lambda { StateDefaultNil[:HI].should be_nil }.should_not raise_error
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'cache layer' do
|
34
|
+
let(:florida) { State.new(abbreviation: 'FL', name: 'Florida') }
|
35
|
+
|
36
|
+
it 'caches found records' do
|
37
|
+
scope = mock(first: florida)
|
38
|
+
State.should_receive(:where).once.and_return(scope)
|
39
|
+
|
40
|
+
2.times { State[:FL].name.should eq 'Florida' }
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'in test environment' do
|
44
|
+
before { Object.const_set('Rails', mock(env: mock(test?: true), cache: ActiveSupport::Cache::MemoryStore.new)) }
|
45
|
+
after { Object.send(:remove_const, 'Rails') }
|
46
|
+
|
47
|
+
it 'forces cache miss' do
|
48
|
+
scope = mock(first: florida)
|
49
|
+
State.should_receive(:where).twice.and_return(scope)
|
50
|
+
|
51
|
+
2.times { State[:FL].name.should eq 'Florida' }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'clears the cache on save' do
|
56
|
+
scope = mock(first: florida)
|
57
|
+
State.should_receive(:where).twice.and_return(scope)
|
58
|
+
|
59
|
+
State[:FL].save
|
60
|
+
State[:FL]
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'clears the cache on destroy' do
|
64
|
+
scope = mock(first: florida)
|
65
|
+
State.should_receive(:where).twice.and_return(scope)
|
66
|
+
|
67
|
+
State[:FL].destroy
|
68
|
+
State[:FL]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hashdown::SelectOptions do
|
4
|
+
let(:state_names) { %w(Arizona California Colorado New\ York Texas) }
|
5
|
+
|
6
|
+
it 'adds #select_options to ActiveRecord classes' do
|
7
|
+
State.should respond_to(:select_options)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'returns one entry per database record' do
|
11
|
+
State.select_options.length.should eq State.count
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'sorts by label if no order is specified' do
|
15
|
+
State.select_options.map(&:first).should eq state_names
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'respects order if specified' do
|
19
|
+
Currency.select_options.map(&:first).should eq ['Renminbi', 'Euro', 'Pound Sterling', 'US Dollar']
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns a list of records formatted for the #select form helper' do
|
23
|
+
option = State.select_options.first
|
24
|
+
option.first.should eq 'Arizona'
|
25
|
+
option.last.should eq State[:AZ].id
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'generates grouped options' do
|
29
|
+
grouped_states = State.select_options(group: lambda{|state| state.name.first })
|
30
|
+
grouped_states.map(&:first).should eq %w( A C N T )
|
31
|
+
grouped_states.detect{|group, states| group == 'C' }.last.length.should eq 2
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'supports alternate option labels' do
|
35
|
+
Currency.select_options(:code).map(&:first).should eq %w( CNY EUR GBP USD )
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'supports alternate option values' do
|
39
|
+
Currency.select_options(:name, :code).map(&:last).should eq %w( CNY EUR GBP USD )
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'cache layer' do
|
43
|
+
let(:states) { State.all }
|
44
|
+
let(:mock_scope) { mock(arel: mock(orders: %w( name )), to_sql: "SELECT * FROM states") }
|
45
|
+
|
46
|
+
it 'should cache found records' do
|
47
|
+
mock_scope.should_receive(:all).once.and_return(states)
|
48
|
+
State.stub(:scoped).and_return(mock_scope)
|
49
|
+
|
50
|
+
2.times { State.select_options.length.should eq states.length }
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'in test environment' do
|
54
|
+
before { Object.const_set('Rails', mock(env: mock(test?: true), cache: ActiveSupport::Cache::MemoryStore.new)) }
|
55
|
+
after { Object.send(:remove_const, 'Rails') }
|
56
|
+
|
57
|
+
it 'forces cache miss' do
|
58
|
+
mock_scope.should_receive(:all).twice.and_return(states)
|
59
|
+
State.stub(:scoped).and_return(mock_scope)
|
60
|
+
|
61
|
+
2.times { State.select_options.length.should eq states.length }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'respects scopes' do
|
66
|
+
State.select_options.map(&:first).should eq state_names
|
67
|
+
State.starting_with_c.select_options.map(&:first).should eq %w( California Colorado )
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'clears the cache on save' do
|
71
|
+
mock_scope.should_receive(:all).twice.and_return(states)
|
72
|
+
State.stub(:scoped).and_return(mock_scope)
|
73
|
+
|
74
|
+
State.select_options
|
75
|
+
states.first.save
|
76
|
+
State.select_options
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'clears the cache on destroy' do
|
80
|
+
mock_scope.should_receive(:all).twice.and_return(states)
|
81
|
+
State.stub(:scoped).and_return(mock_scope)
|
82
|
+
|
83
|
+
State.select_options
|
84
|
+
states.first.destroy
|
85
|
+
State.select_options
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'pry-nav'
|
2
|
+
require 'rspec'
|
3
|
+
require 'rspec/autorun'
|
4
|
+
|
5
|
+
require 'sqlite3'
|
6
|
+
require 'active_record'
|
7
|
+
require 'hashdown/finder'
|
8
|
+
require 'hashdown/select_options'
|
9
|
+
|
10
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
11
|
+
load 'support/models.rb'
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.mock_with :rspec
|
15
|
+
|
16
|
+
config.before(:each) do
|
17
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
18
|
+
load 'support/schema.rb'
|
19
|
+
load 'support/seeds.rb'
|
20
|
+
|
21
|
+
Hashdown.cache = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
config.after(:each) do
|
25
|
+
Hashdown.cache = nil
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class State < ActiveRecord::Base
|
2
|
+
scope :starting_with_c, where("name like 'C%'")
|
3
|
+
finder :abbreviation
|
4
|
+
end
|
5
|
+
|
6
|
+
class SortedState < ActiveRecord::Base
|
7
|
+
self.table_name = 'states'
|
8
|
+
default_scope order(:abbreviation)
|
9
|
+
finder :abbreviation
|
10
|
+
end
|
11
|
+
|
12
|
+
class StateDefaultNil < ActiveRecord::Base
|
13
|
+
self.table_name = 'states'
|
14
|
+
finder :abbreviation, default: nil
|
15
|
+
end
|
16
|
+
|
17
|
+
class Currency < ActiveRecord::Base
|
18
|
+
default_scope order(:code)
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class ActiveRecord::Base
|
2
|
+
def self.seed(data_table)
|
3
|
+
data_table = data_table.strip.split(/\s*\n\s*/)
|
4
|
+
data_table.reject!{|row| row =~ /---/ }
|
5
|
+
|
6
|
+
headers = data_table.shift.split(/\s*\|\s*/)[1..-1]
|
7
|
+
|
8
|
+
delete_all
|
9
|
+
|
10
|
+
data_table.each do |row|
|
11
|
+
data = Hash[*headers.zip(row.split(/\s*\|\s*/)[1..-1]).flatten].with_indifferent_access
|
12
|
+
if data[primary_key]
|
13
|
+
connection.execute("INSERT INTO #{table_name} SET #{ data.map{|k,v| "#{ connection.quote_column_name(k) } = #{ connection.quote(v) }" }.join(', ') }")
|
14
|
+
else
|
15
|
+
create(data)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
State.seed %q{
|
22
|
+
+--------------+------------+
|
23
|
+
| abbreviation | name |
|
24
|
+
+--------------+------------+
|
25
|
+
| AZ | Arizona |
|
26
|
+
| NY | New York |
|
27
|
+
| CA | California |
|
28
|
+
| TX | Texas |
|
29
|
+
| CO | Colorado |
|
30
|
+
+--------------+------------+
|
31
|
+
}
|
32
|
+
|
33
|
+
Currency.seed %q{
|
34
|
+
+------+----------------+
|
35
|
+
| code | name |
|
36
|
+
+------+----------------+
|
37
|
+
| GBP | Pound Sterling |
|
38
|
+
| EUR | Euro |
|
39
|
+
| USD | US Dollar |
|
40
|
+
| CNY | Renminbi |
|
41
|
+
+------+----------------+
|
42
|
+
}
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hashdown
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Solomon White
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: &70301816154100 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70301816154100
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: pry-nav
|
27
|
+
requirement: &70301816153640 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70301816153640
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70301816152900 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70301816152900
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &70301815747100 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70301815747100
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: sqlite3
|
60
|
+
requirement: &70301815746680 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70301815746680
|
69
|
+
description: Hashdown
|
70
|
+
email:
|
71
|
+
- rubysolo@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- hashdown.gemspec
|
82
|
+
- lib/hashdown.rb
|
83
|
+
- lib/hashdown/cache.rb
|
84
|
+
- lib/hashdown/finder.rb
|
85
|
+
- lib/hashdown/select_options.rb
|
86
|
+
- lib/hashdown/version.rb
|
87
|
+
- spec/lib/hashdown/cache_spec.rb
|
88
|
+
- spec/lib/hashdown/finder_spec.rb
|
89
|
+
- spec/lib/hashdown/select_options_spec.rb
|
90
|
+
- spec/spec_helper.rb
|
91
|
+
- spec/support/models.rb
|
92
|
+
- spec/support/schema.rb
|
93
|
+
- spec/support/seeds.rb
|
94
|
+
homepage: https://github.com/rubysolo/hashdown
|
95
|
+
licenses: []
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
hash: -1096798538704753083
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
segments:
|
116
|
+
- 0
|
117
|
+
hash: -1096798538704753083
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 1.8.11
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: super lightweight Rails plugin that adds hash-style lookups and option list
|
124
|
+
(for generating dropdowns) to ActiveRecord models
|
125
|
+
test_files:
|
126
|
+
- spec/lib/hashdown/cache_spec.rb
|
127
|
+
- spec/lib/hashdown/finder_spec.rb
|
128
|
+
- spec/lib/hashdown/select_options_spec.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
- spec/support/models.rb
|
131
|
+
- spec/support/schema.rb
|
132
|
+
- spec/support/seeds.rb
|