expose_association 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/README.md +48 -0
- data/Rakefile +1 -0
- data/expose_association.gemspec +22 -0
- data/lib/expose_association/base.rb +88 -0
- data/lib/expose_association/railtie.rb +9 -0
- data/lib/expose_association/version.rb +3 -0
- data/lib/expose_association.rb +3 -0
- data/spec/expose_association/base_spec.rb +92 -0
- data/spec/spec_helper.rb +18 -0
- metadata +105 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Expose Association
|
2
|
+
|
3
|
+
|
4
|
+
## Usage:
|
5
|
+
|
6
|
+
Suppose you have this two classes:
|
7
|
+
|
8
|
+
<pre>
|
9
|
+
class Address < ActiveRecord::Base
|
10
|
+
belongs_to :contact
|
11
|
+
end
|
12
|
+
|
13
|
+
class Contact < ActiveRecord::Base
|
14
|
+
|
15
|
+
has_one :primary_address, :class_name => 'Address'
|
16
|
+
expose_association :primary_address, prefix: true, class: Address do
|
17
|
+
expose :address_line_1, :address_line_2, :city
|
18
|
+
expose :phone_number, prefix: false
|
19
|
+
expose :country, prefix: 'primary'
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
</pre>
|
25
|
+
|
26
|
+
You'll be able to use this class like this:
|
27
|
+
|
28
|
+
<pre>
|
29
|
+
contact = Contact.new
|
30
|
+
contact.primary_address_address_line_1 = "122 Fake St."
|
31
|
+
contact.primary_address_address_line_2 = "Apt. 45"
|
32
|
+
contact.primary_address_city = "Beverly Hill"
|
33
|
+
contact.phone_number = "123-123-1234"
|
34
|
+
contact.primary_country = "Argentina"
|
35
|
+
contact.save
|
36
|
+
</pre>
|
37
|
+
|
38
|
+
Specs?
|
39
|
+
|
40
|
+
<pre>
|
41
|
+
contact.primary_address.should be_a_kind_of(Address)
|
42
|
+
...
|
43
|
+
|
44
|
+
# Check te real ones on the spec folder
|
45
|
+
|
46
|
+
</pre>
|
47
|
+
|
48
|
+
MIT License.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/expose_association/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Lucas Florio"]
|
6
|
+
gem.email = ["lucasefe@gmail.com"]
|
7
|
+
gem.description = %q{Easy exposition of has_one associations at the object
|
8
|
+
level, to avoid nesting.}
|
9
|
+
gem.summary = %q{...}
|
10
|
+
gem.homepage = ""
|
11
|
+
|
12
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
gem.files = `git ls-files`.split("\n")
|
14
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
gem.name = "expose_association"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = ExposeAssociation::VERSION
|
18
|
+
gem.add_dependency 'activerecord'
|
19
|
+
gem.add_development_dependency 'rspec'
|
20
|
+
gem.add_development_dependency 'sqlite3'
|
21
|
+
gem.add_development_dependency 'shoulda'
|
22
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module ExposeAssociation
|
2
|
+
module Base
|
3
|
+
def expose_association association_name, options = {}, &block
|
4
|
+
exposer = AssociationExposer.new(self, association_name, options)
|
5
|
+
setup_class_for_exposition(exposer)
|
6
|
+
if block_given?
|
7
|
+
exposer.instance_eval(&block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup_class_for_exposition(exposer)
|
12
|
+
# allowing mass assignment of the has_one association
|
13
|
+
self.attr_accessible exposer.association_attributes
|
14
|
+
self.accepts_nested_attributes_for exposer.association_name
|
15
|
+
self.send :alias_method, "old_#{exposer.association_name}", exposer.association_name
|
16
|
+
self.send :alias_method, "old_#{exposer.association_name}=", "#{exposer.association_name}="
|
17
|
+
self.class_eval <<-CODE
|
18
|
+
def #{exposer.association_name}
|
19
|
+
#{exposer.association_setup_method}
|
20
|
+
end
|
21
|
+
|
22
|
+
def #{exposer.association_setup_method}
|
23
|
+
self.old_#{exposer.association_name} ||= #{exposer.association_class}.new
|
24
|
+
end
|
25
|
+
CODE
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class AssociationExposer
|
30
|
+
|
31
|
+
attr_reader :association_name, :association_class
|
32
|
+
|
33
|
+
def initialize klass, association_name, options = {}
|
34
|
+
@klass = klass
|
35
|
+
options[:class] ||= association_name.to_s.camelize.constantize
|
36
|
+
@association_class = options[:class]
|
37
|
+
@association_name = association_name
|
38
|
+
@options = options
|
39
|
+
end
|
40
|
+
|
41
|
+
def expose *attributes
|
42
|
+
options = attributes.last.is_a?(Hash) ? attributes.pop : {}
|
43
|
+
attributes.each do |attribute|
|
44
|
+
expose_attribute attribute, options
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def expose_attribute name, options = {}
|
49
|
+
@klass.delegate name, "#{name}=",
|
50
|
+
to: association_setup_method,
|
51
|
+
prefix: prefix_for(options[:prefix])
|
52
|
+
|
53
|
+
@klass.attr_accessible attr_name_for(name, options[:prefix])
|
54
|
+
end
|
55
|
+
|
56
|
+
def attr_name_for(name, prefix)
|
57
|
+
case prefix
|
58
|
+
when FalseClass then name.to_s
|
59
|
+
when String, Symbol then
|
60
|
+
"#{prefix.to_s}_#{name}"
|
61
|
+
else
|
62
|
+
"#{association_name}_#{name}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def prefix_for(prefix_option)
|
67
|
+
case prefix_option
|
68
|
+
when TrueClass, NilClass then association_name.to_s
|
69
|
+
when FalseClass then false
|
70
|
+
when String, Symbol then prefix_option.to_s
|
71
|
+
else # nil
|
72
|
+
raise "Wrong value for prefix: #{prefix_option}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def association_attributes
|
77
|
+
"#{association_name}_attributes"
|
78
|
+
end
|
79
|
+
|
80
|
+
def association_setup_method
|
81
|
+
"#{setup_method_prefix}_#{@association_name}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def setup_method_prefix
|
85
|
+
@options.fetch(:setup_method_prefix, "setup")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Address < ActiveRecord::Base; end
|
4
|
+
class Contact < ActiveRecord::Base
|
5
|
+
extend ExposeAssociation::Base
|
6
|
+
has_one :main_location, :class_name => 'Address'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'SomeModel with ExposeAssociation' do
|
10
|
+
|
11
|
+
it "should include the basic functionality" do
|
12
|
+
Contact.should respond_to(:expose_association)
|
13
|
+
end
|
14
|
+
|
15
|
+
context "exposing another object" do
|
16
|
+
|
17
|
+
before :all do
|
18
|
+
Contact.expose_association :main_location, class: Address do
|
19
|
+
expose :city
|
20
|
+
expose :address_line_1, prefix: false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:contact) { Contact.new }
|
25
|
+
|
26
|
+
it "should setup the association with the provided class" do
|
27
|
+
contact.main_location.should be_a_kind_of(Address)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should add the builder" do
|
31
|
+
contact.should respond_to(:setup_main_location)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should support mass assignment of the other object" do
|
35
|
+
contact.should allow_mass_assignment_of(:main_location_attributes)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should expose a given attribute (with prefix)" do
|
39
|
+
contact.should respond_to("main_location_city")
|
40
|
+
contact.should respond_to("main_location_city=")
|
41
|
+
contact.main_location_city = "Madrid"
|
42
|
+
contact.main_location.city.should == "Madrid"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should allow mass assignment of the exposed attributes" do
|
46
|
+
contact = Contact.new main_location_city: "Barcelona"
|
47
|
+
contact.main_location.city.should == "Barcelona"
|
48
|
+
end
|
49
|
+
|
50
|
+
context "with no prefix" do
|
51
|
+
it "should expose the attribute directly on the main object" do
|
52
|
+
contact.should respond_to(:address_line_1)
|
53
|
+
contact.address_line_1 = "Freire 1008"
|
54
|
+
contact.main_location.address_line_1.should == "Freire 1008"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
describe ExposeAssociation::AssociationExposer do
|
63
|
+
it "should know the setup method" do
|
64
|
+
exposer = ExposeAssociation::AssociationExposer.new(Contact, :main_location, class: Address)
|
65
|
+
exposer.association_setup_method.should == "setup_main_location"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should infer the class name of the association if none is provided" do
|
69
|
+
exposer = ExposeAssociation::AssociationExposer.new(Contact, :address)
|
70
|
+
exposer.association_class.should == Address
|
71
|
+
exposer = ExposeAssociation::AssociationExposer.new(Contact, :main_location, class: Address)
|
72
|
+
exposer.association_class.should == Address
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should calculate properly the attr_name for a given prefix" do
|
76
|
+
exposer = ExposeAssociation::AssociationExposer.new(Contact, :address)
|
77
|
+
exposer.attr_name_for(:city, nil).should == 'address_city'
|
78
|
+
exposer.attr_name_for(:city, true).should == 'address_city'
|
79
|
+
exposer.attr_name_for(:city, false).should == 'city'
|
80
|
+
exposer.attr_name_for(:city, "culo").should == 'culo_city'
|
81
|
+
exposer.attr_name_for(:city, :culito).should == 'culito_city'
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should calculate properly the prefix " do
|
85
|
+
exposer = ExposeAssociation::AssociationExposer.new(Contact, :address)
|
86
|
+
exposer.prefix_for("culito").should == 'culito'
|
87
|
+
exposer.prefix_for(true).should == 'address'
|
88
|
+
exposer.prefix_for(nil).should == 'address'
|
89
|
+
exposer.prefix_for(false).should == false
|
90
|
+
exposer.prefix_for(:culito).should == 'culito'
|
91
|
+
end
|
92
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'expose_association'
|
3
|
+
require 'shoulda'
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
6
|
+
|
7
|
+
ActiveRecord::Schema.define(:version => 1) do
|
8
|
+
create_table :contacts do |t|
|
9
|
+
t.string :first_name, :last_name
|
10
|
+
end
|
11
|
+
|
12
|
+
create_table :addresses do |t|
|
13
|
+
t.string :contactable_type
|
14
|
+
t.integer :contactable_id
|
15
|
+
t.string :address_line_1, :address_line_2
|
16
|
+
t.string :city
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: expose_association
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lucas Florio
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: &70160113896320 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70160113896320
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70160113895020 !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: *70160113895020
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: sqlite3
|
38
|
+
requirement: &70160113893880 !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: *70160113893880
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: shoulda
|
49
|
+
requirement: &70160113892360 !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: *70160113892360
|
58
|
+
description: ! "Easy exposition of has_one associations at the object\n level, to
|
59
|
+
avoid nesting."
|
60
|
+
email:
|
61
|
+
- lucasefe@gmail.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- .rspec
|
68
|
+
- Gemfile
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- expose_association.gemspec
|
72
|
+
- lib/expose_association.rb
|
73
|
+
- lib/expose_association/base.rb
|
74
|
+
- lib/expose_association/railtie.rb
|
75
|
+
- lib/expose_association/version.rb
|
76
|
+
- spec/expose_association/base_spec.rb
|
77
|
+
- spec/spec_helper.rb
|
78
|
+
homepage: ''
|
79
|
+
licenses: []
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.10
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: ! '...'
|
102
|
+
test_files:
|
103
|
+
- spec/expose_association/base_spec.rb
|
104
|
+
- spec/spec_helper.rb
|
105
|
+
has_rdoc:
|