relation_builder 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +102 -0
- data/Rakefile +2 -0
- data/lib/relation_builder/ext_deep_merge.rb +27 -0
- data/lib/relation_builder/initialize_options.rb +157 -0
- data/lib/relation_builder/version.rb +3 -0
- data/lib/relation_builder.rb +13 -0
- data/relation_builder.gemspec +26 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8a7965b9fa26cb3ba5dfe81e8d31d91c4b10b872
|
4
|
+
data.tar.gz: 80e5e297a97bb39f0c80beba994829fca037c026
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 317cdd2b155b97d52e459e8d7e862278eee786b9ae5f10b728de3a936a157a4c55bcae495671de10b0a15ef6f4df4aa538c95e9b7861c5da27c9c04824142c43
|
7
|
+
data.tar.gz: 8212149aff8c94d530f5b41f93b2bc16a9f4515e26b6fb32dd21e53a4122238a330e9998aebd338ed24903fd3bf63112fca28b6bce377df935be8142cb732b17
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Ivan Zabrovskiy
|
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,102 @@
|
|
1
|
+
# RelationBuilder
|
2
|
+
|
3
|
+
This is a gem for easy build of nested relations through options of initialize
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'relation_builder'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install relation_builder
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Examples
|
24
|
+
|
25
|
+
Old style of initialize several nested objects:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
building = Building.new(params)
|
29
|
+
flat = Flat.new
|
30
|
+
room = Room.new
|
31
|
+
bathroom = Bathroom.new
|
32
|
+
flat.rooms << room
|
33
|
+
flat.bathroom = bathroom
|
34
|
+
building.flats << flat
|
35
|
+
```
|
36
|
+
|
37
|
+
Initializing with relation_builder:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
building = Building.new(params, auto_build_relations: {flats: [:rooms, :bathroom]})
|
41
|
+
```
|
42
|
+
|
43
|
+
### How to install?
|
44
|
+
|
45
|
+
In additions to `gem instal` you need to
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
include RelationBuilder::InitializeOptions
|
49
|
+
```
|
50
|
+
|
51
|
+
into all models involved in nested build.
|
52
|
+
|
53
|
+
## Strategy of relations build
|
54
|
+
|
55
|
+
There is two strategy: "nested" and "build". They can be specified by passing
|
56
|
+
`build_strategy: :build/:nested` into options of initialize.
|
57
|
+
Bydefault using `:nested` strategy.
|
58
|
+
|
59
|
+
### 'Nested' strategy
|
60
|
+
|
61
|
+
It use [nested_attributes](http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html)
|
62
|
+
for build nested relations. If you have semifiled params. For example, in params you have attributes for building,
|
63
|
+
flat and room. And suppose that in any case you want to have a bathroom. So you can say same thig as in previous
|
64
|
+
example:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
building = Building.new(params, auto_build_relations: {flat: [:room, :bathroom]})
|
68
|
+
```
|
69
|
+
|
70
|
+
Here you keep all attributes of nested models extracted from params.
|
71
|
+
And additionally in any case for each flat you get a bathroom.
|
72
|
+
|
73
|
+
### 'Build' strategy
|
74
|
+
|
75
|
+
It based on simple build association. It cat be useful if you doesn't want to have a deal with
|
76
|
+
[nested_attributes](http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html).
|
77
|
+
This strategy has same syntax with `auto_build_relations` key, but have several restrictions.
|
78
|
+
|
79
|
+
Positive moments:
|
80
|
+
|
81
|
+
* you can build a relations for [ActiveRecord](http://www.rubydoc.info/gems/activerecord/) and
|
82
|
+
for [Mongoid](https://github.com/mongoid/mongoid).
|
83
|
+
|
84
|
+
Negative moments:
|
85
|
+
|
86
|
+
* this strategy can't build rest of nesting relation, only full chain, i.e. this strategy is incompatible with
|
87
|
+
[nested_attributes](http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html).
|
88
|
+
|
89
|
+
## Purpose
|
90
|
+
|
91
|
+
One of aim of this gem is easy way to deal with [formtastic](https://github.com/justinfrench/formtastic) form.
|
92
|
+
If you want to make a form with several nested object you must initialize all objects before send them
|
93
|
+
into the form. Otherwise you can't see any fields of nested object on the form. For many nested obeject it can be
|
94
|
+
annoying.
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
1. Fork it ( https://github.com/Loriowar/relation_builder/fork )
|
99
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
100
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
101
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
102
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module RelationBuilder
|
2
|
+
module ExtDeepMerge
|
3
|
+
# Alternative for native method of Hash
|
4
|
+
# It call block for any coincide keys even if values is Hash too
|
5
|
+
def ext_deep_merge(other_hash, &block)
|
6
|
+
dup.ext_deep_merge!(other_hash, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Same as +ext_deep_merge+, but modifies +self+.
|
10
|
+
def ext_deep_merge!(other_hash, &block)
|
11
|
+
other_hash.each_pair do |current_key, other_value|
|
12
|
+
this_value = self[current_key]
|
13
|
+
self[current_key] =
|
14
|
+
if block_given? && key?(current_key)
|
15
|
+
block.call(current_key, this_value, other_value)
|
16
|
+
else
|
17
|
+
if this_value.is_a?(Hash) && other_value.is_a?(Hash)
|
18
|
+
this_value.ext_deep_merge(other_value, &block)
|
19
|
+
else
|
20
|
+
other_value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module RelationBuilder
|
2
|
+
module InitializeOptions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
alias_method_chain :initialize, :build_relation
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Chain initialize method for support additional options
|
12
|
+
def initialize_with_build_relation(new_attributes = nil, options = {})
|
13
|
+
# relation build strategy:
|
14
|
+
# nested - using nested_attributes
|
15
|
+
# build - using standard build method
|
16
|
+
nested_build_strategy = options[:build_strategy] || :nested
|
17
|
+
processed_attributes = new_attributes || {}
|
18
|
+
auto_build_relation_list = options.delete(:auto_build_relations)
|
19
|
+
|
20
|
+
if nested_build_strategy == :nested && auto_build_relation_list.present?
|
21
|
+
nested_relations_attributes = create_nested_options(auto_build_relation_list)
|
22
|
+
processed_attributes = nested_deep_merge(processed_attributes, nested_relations_attributes)
|
23
|
+
end
|
24
|
+
|
25
|
+
# store return value to save default behaviour of initialize
|
26
|
+
ret_val = initialize_without_build_relation(processed_attributes, options)
|
27
|
+
|
28
|
+
if nested_build_strategy == :build && auto_build_relation_list.present?
|
29
|
+
if auto_build_relation_list.is_a?(Array)
|
30
|
+
auto_build_relation_list.each do |rel|
|
31
|
+
build_relation_with_options(rel)
|
32
|
+
end
|
33
|
+
elsif auto_build_relation_list.is_a?(Hash)
|
34
|
+
auto_build_relation_list.each do |rel, nested_options|
|
35
|
+
build_relation_with_options(rel, auto_build_relations: nested_options)
|
36
|
+
end
|
37
|
+
elsif auto_build_relation_list.is_a?(Symbol) || auto_build_relation_list.is_a?(String)
|
38
|
+
build_relation_with_options(auto_build_relation_list)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
ret_val
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get onformation about relation
|
46
|
+
def reflection_for(method)
|
47
|
+
if self.class.respond_to?(:reflect_on_association)
|
48
|
+
self.class.reflect_on_association(method)
|
49
|
+
elsif self.class.respond_to?(:associations) # MongoMapper uses the 'associations(method)' instead
|
50
|
+
self.class.associations[method]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Build relation based on type of it
|
55
|
+
def build_relation_with_options(rel, options = {})
|
56
|
+
reflection = reflection_for(rel)
|
57
|
+
if reflection.present?
|
58
|
+
relation = instance_eval(rel.to_s)
|
59
|
+
if %i(has_and_belongs_to_many
|
60
|
+
has_many
|
61
|
+
references_and_referenced_in_many
|
62
|
+
references_many).include?(reflection.macro)
|
63
|
+
if relation.blank?
|
64
|
+
relation << reflection.klass.new({}, options)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
if relation.blank?
|
68
|
+
self.send("#{rel}=", reflection.klass.new({}, options))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create nested Hash with attributes for using in nested_attributes
|
75
|
+
# Example:
|
76
|
+
# input: {versions:
|
77
|
+
# {doc_kit_source: [:source, :author]},
|
78
|
+
# product: :project}
|
79
|
+
# Output: {versions_attributes:
|
80
|
+
# {doc_kit_source_attributes:
|
81
|
+
# {source_attributes: {},
|
82
|
+
# author_attributes: {}
|
83
|
+
# }
|
84
|
+
# },
|
85
|
+
# product_attributes:
|
86
|
+
# {project_attributes: {}}
|
87
|
+
# }
|
88
|
+
#
|
89
|
+
def create_nested_options(build_options)
|
90
|
+
new_attributes = {}
|
91
|
+
if build_options.is_a?(Hash)
|
92
|
+
build_options.each do |required_rel, nested_rels|
|
93
|
+
new_attributes.merge!(rel_name_to_nested_attrs(required_rel))
|
94
|
+
nested_key = to_nested_attributes_key(required_rel)
|
95
|
+
if nested_rels.is_a?(Symbol)
|
96
|
+
new_attributes[nested_key].merge!(rel_name_to_nested_attrs(nested_rels))
|
97
|
+
elsif nested_rels.is_a?(Array)
|
98
|
+
nested_rels.each do |nested_rel|
|
99
|
+
new_attributes[nested_key].merge!(rel_name_to_nested_attrs(nested_rel))
|
100
|
+
end
|
101
|
+
elsif nested_rels.is_a?(Hash)
|
102
|
+
new_attributes[nested_key].merge!(create_nested_options(nested_rels))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
new_attributes
|
108
|
+
end
|
109
|
+
|
110
|
+
# Generate key-name for nested_attributes from relation name
|
111
|
+
def to_nested_attributes_key(relation_name)
|
112
|
+
"#{relation_name}_attributes".to_sym
|
113
|
+
end
|
114
|
+
|
115
|
+
# Hash for passing into nested_attributes for build single relation
|
116
|
+
def rel_name_to_nested_attrs(relation_name)
|
117
|
+
{to_nested_attributes_key(relation_name) => {}}
|
118
|
+
end
|
119
|
+
|
120
|
+
# Merge of initialize params and additional options for build relations through nested_attributes
|
121
|
+
def nested_deep_merge(master, slave)
|
122
|
+
# Add alternative deep_merge method for instance of Hash
|
123
|
+
extend_deep_merge = ->(obj){obj.send(:extend, RelationBuilder::ExtDeepMerge) unless master.is_a? RelationBuilder::ExtDeepMerge; obj}
|
124
|
+
|
125
|
+
extend_deep_merge.call(master)
|
126
|
+
extend_deep_merge.call(slave)
|
127
|
+
|
128
|
+
# extend_deep_merge call block for any coincide keys
|
129
|
+
master.ext_deep_merge(slave) do |_, master_val, slave_val|
|
130
|
+
# special processing of attributes for has_many relation (see nested_attributes documentation for details)
|
131
|
+
if master_val.is_a?(Hash) && master_val.all?{|k, _| key_is_numeric?(k)}
|
132
|
+
extend_deep_merge.call(master_val)
|
133
|
+
master_val.inject(extend_deep_merge.call({})) do |h, (inner_key, value)|
|
134
|
+
h[inner_key] = nested_deep_merge(value, slave_val)
|
135
|
+
h
|
136
|
+
end
|
137
|
+
elsif master_val.is_a?(Hash) && slave_val.is_a?(Hash)
|
138
|
+
extend_deep_merge.call(master_val)
|
139
|
+
extend_deep_merge.call(slave_val)
|
140
|
+
|
141
|
+
nested_deep_merge(master_val, slave_val)
|
142
|
+
else
|
143
|
+
master_val
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def key_is_numeric?(key)
|
149
|
+
!(key =~ /^\d+$/).nil?
|
150
|
+
end
|
151
|
+
|
152
|
+
module ClassMethods
|
153
|
+
# stub
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'relation_builder/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "relation_builder"
|
8
|
+
spec.version = RelationBuilder::VERSION
|
9
|
+
spec.authors = ["Ivan Zabrovskiy"]
|
10
|
+
spec.email = ["loriowar@gmail.com"]
|
11
|
+
spec.summary = %q{This is a gem for easy build of nested relations through options of initialize}
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = "https://github.com/Loriowar/relation_builder"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activerecord", ">= 3.2"
|
22
|
+
spec.add_dependency "activesupport", ">= 3.2"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: relation_builder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ivan Zabrovskiy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
description: This is a gem for easy build of nested relations through options of initialize
|
70
|
+
email:
|
71
|
+
- loriowar@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/relation_builder.rb
|
82
|
+
- lib/relation_builder/ext_deep_merge.rb
|
83
|
+
- lib/relation_builder/initialize_options.rb
|
84
|
+
- lib/relation_builder/version.rb
|
85
|
+
- relation_builder.gemspec
|
86
|
+
homepage: https://github.com/Loriowar/relation_builder
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.0.2
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: This is a gem for easy build of nested relations through options of initialize
|
110
|
+
test_files: []
|
111
|
+
has_rdoc:
|