activerecord-traits 1.0.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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +62 -0
  6. data/Rakefile +1 -0
  7. data/activerecord-traits.gemspec +24 -0
  8. data/lib/activerecord-traits.rb +44 -0
  9. data/lib/traits/association.rb +39 -0
  10. data/lib/traits/attribute.rb +52 -0
  11. data/lib/traits/base.rb +7 -0
  12. data/lib/traits/concerns/association/essay_shortcuts.rb +9 -0
  13. data/lib/traits/concerns/association/intermediate.rb +55 -0
  14. data/lib/traits/concerns/association/join.rb +89 -0
  15. data/lib/traits/concerns/association/macro.rb +43 -0
  16. data/lib/traits/concerns/association/members.rb +35 -0
  17. data/lib/traits/concerns/association/naming.rb +15 -0
  18. data/lib/traits/concerns/association/polymorphism.rb +118 -0
  19. data/lib/traits/concerns/association/read_only.rb +17 -0
  20. data/lib/traits/concerns/association/through.rb +72 -0
  21. data/lib/traits/concerns/attribute/essay_shortcuts.rb +9 -0
  22. data/lib/traits/concerns/attribute/key.rb +38 -0
  23. data/lib/traits/concerns/attribute/naming.rb +20 -0
  24. data/lib/traits/concerns/attribute/polymorphism.rb +28 -0
  25. data/lib/traits/concerns/attribute/querying.rb +14 -0
  26. data/lib/traits/concerns/attribute/sti.rb +15 -0
  27. data/lib/traits/concerns/attribute/type.rb +59 -0
  28. data/lib/traits/concerns/model/essay_shortcuts.rb +9 -0
  29. data/lib/traits/concerns/model/naming.rb +83 -0
  30. data/lib/traits/concerns/model/polymorphism.rb +15 -0
  31. data/lib/traits/concerns/model/querying.rb +34 -0
  32. data/lib/traits/concerns/model/sti.rb +41 -0
  33. data/lib/traits/descendants_listing.rb +51 -0
  34. data/lib/traits/engine.rb +13 -0
  35. data/lib/traits/list.rb +52 -0
  36. data/lib/traits/model.rb +78 -0
  37. data/lib/traits/utilities.rb +37 -0
  38. data/lib/traits/version.rb +3 -0
  39. metadata +151 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c19a931c6cf49fc903d0a26157fcee1e76c71115
4
+ data.tar.gz: 572102c9adbf4c8e0a751e9b18cdaac4bfb40620
5
+ SHA512:
6
+ metadata.gz: ef3f143af6c3ec68b3f6c92102b4a581b2b60dc6b32a7a8e4eed0359c3e53b8e337026c699fc33715aad29f0b61ba9a0fa5bd90bb32d3bf35cd97667c68ad787
7
+ data.tar.gz: 6e6c4af699dd5e632e07e31c37c1d7f744722ad4f4879f120da471390a6a6a4c6f79ab19e91238f339d2026b60201b4e5750dc96e7c0abe9d1c633fc4b903d12
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in confo.gemspec
4
+ gemspec
5
+
6
+ gem 'essay', path: '~/Development/essay'
7
+ gem 'essay-globalize', path: '~/Development/essay-globalize'
8
+ gem 'essay-carrierwave', path: '~/Development/essay-carrierwave'
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 Yaroslav Konoplov
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.
@@ -0,0 +1,62 @@
1
+ # ActiveRecord Type Traits
2
+
3
+ ##### This library provides a series of classes to obtain type information of ActiveRecord models, attributes and associations.
4
+ This library is focused only on standard ActiveRecord features and will not provide information for ActiveRecord plugins such as Globalize, CarrierWave, Paranoid and others.
5
+
6
+ API design is done in good and clear manner for best understanding.
7
+
8
+ **All base ActiveRecord features are supported.**
9
+
10
+ **Supported advanced ActiveRecord features:**
11
+
12
+ 1. `has_many`, `has_one`, `has_and_belongs_to_many`, `belongs_to` associations
13
+ 2. Intermediate and through (deep) associations
14
+ 3. Polymorphic associations
15
+ 4. Single Table Inheritance
16
+ 5. SQL Join metadata
17
+
18
+ ## Example of usage
19
+ ```ruby
20
+
21
+ class Author < ActiveRecord::Base
22
+ has_many :articles
23
+ has_one :photo, as: :imageable
24
+ end
25
+
26
+ class Article < ActiveRecord::Base
27
+ belongs_to :author
28
+ end
29
+
30
+ class Photo
31
+ belongs_to :imageable, polymorphic: true
32
+ end
33
+
34
+ Author.traits.associations[:articles].has_many? # => true
35
+ Author.traits.associations[:articles].to_class # => Article
36
+ Author.traits.associations[:articles].paired_through_polymorphism? # => true
37
+
38
+ Photo.traits.associations[:imageable].polymorphic? # => true
39
+ Photo.traits.associations[:imageable].accepted_classes_through_polymorphism # => [Author]
40
+ Photo.traits.attributes[:imageable_type].polymorphic_type? # => true
41
+
42
+ Article.traits.attributes[:author_id].foreign_key? # => true
43
+
44
+ class Present < ActiveRecord::Base
45
+ end
46
+
47
+ class Toy < Present
48
+ end
49
+
50
+ class VideoGame < Present
51
+ end
52
+
53
+ Present.traits.sti_base? # => true
54
+ Toy.traits.sti_derived? # => true
55
+ Toy.traits.sti_chain # => [Present, Toy]
56
+
57
+ ```
58
+
59
+ ## Gemfile
60
+ ```ruby
61
+ gem 'activerecord-traits', github: 'yivo/activerecord-traits'
62
+ ```
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../lib/traits/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'activerecord-traits'
6
+ s.version = Traits::VERSION
7
+ s.authors = ['Yaroslav Konoplov']
8
+ s.email = ['yaroslav@inbox.com']
9
+ s.summary = 'Type information of ActiveRecord models, attributes and associations'
10
+ s.description = 'Type information of ActiveRecord models, attributes and associations'
11
+ s.homepage = 'http://github.com/yivo/activerecord-traits'
12
+ s.license = 'MIT'
13
+
14
+ s.executables = `git ls-files -z -- bin/*`.split("\x0").map{ |f| File.basename(f) }
15
+ s.files = `git ls-files -z`.split("\x0")
16
+ s.test_files = `git ls-files -z -- {test,spec,features}/*`.split("\x0")
17
+ s.require_paths = ['lib']
18
+
19
+ s.add_dependency 'activesupport', '>= 3.2.0'
20
+ s.add_dependency 'activerecord', '>= 3.2.0'
21
+ s.add_dependency 'essay', '~> 1.0.0'
22
+ s.add_dependency 'essay-globalize', '~> 1.0.0'
23
+ s.add_dependency 'essay-carrierwave', '~> 1.0.0'
24
+ end
@@ -0,0 +1,44 @@
1
+ require 'active_support/all'
2
+ require 'active_record'
3
+ require 'essay'
4
+ require 'essay-globalize'
5
+ require 'essay-carrierwave'
6
+
7
+ require 'traits/engine'
8
+ require 'traits/utilities'
9
+ require 'traits/descendants_listing'
10
+ require 'traits/base'
11
+ require 'traits/attribute'
12
+ require 'traits/association'
13
+ require 'traits/model'
14
+ require 'traits/list'
15
+
16
+ class ActiveRecord::Base
17
+ def self.traits
18
+ @traits ||= Traits::Model.new(self)
19
+ end
20
+ end
21
+
22
+ module Traits
23
+ extend Enumerable
24
+
25
+ def self.for(obj)
26
+ retrieve_model_class(obj).traits
27
+ end
28
+
29
+ def self.each(&block)
30
+ active_record_descendants.each { |ar| block.call(ar.traits) }
31
+ end
32
+
33
+ def self.all
34
+ each_with_object({}) do |traits, memo|
35
+ memo[traits.name] = traits
36
+ end
37
+ end
38
+
39
+ def self.to_hash
40
+ each_with_object({}) do |traits, memo|
41
+ memo[traits.name] = traits.to_hash
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ module Traits
2
+ class Association < Base
3
+ end
4
+ end
5
+
6
+ require 'traits/concerns/association/intermediate'
7
+ require 'traits/concerns/association/join'
8
+ require 'traits/concerns/association/macro'
9
+ require 'traits/concerns/association/members'
10
+ require 'traits/concerns/association/naming'
11
+ require 'traits/concerns/association/polymorphism'
12
+ require 'traits/concerns/association/read_only'
13
+ require 'traits/concerns/association/through'
14
+ require 'traits/concerns/association/essay_shortcuts'
15
+
16
+ module Traits
17
+ class Association
18
+ include Members
19
+ include Naming
20
+ include Macro
21
+ include Intermediate
22
+ include Through
23
+ include Join
24
+ include ReadOnly
25
+ include Polymorphism
26
+ include EssayShortcuts
27
+
28
+ attr_reader :reflection
29
+
30
+ def initialize(model_class, reflection)
31
+ @from_class = model_class
32
+ @reflection = reflection
33
+ end
34
+
35
+ def to_s
36
+ "#{from.class_name}##{name}"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ module Traits
2
+ class Attribute < Base
3
+ end
4
+ end
5
+
6
+ require 'traits/concerns/attribute/key'
7
+ require 'traits/concerns/attribute/naming'
8
+ require 'traits/concerns/attribute/polymorphism'
9
+ require 'traits/concerns/attribute/querying'
10
+ require 'traits/concerns/attribute/sti'
11
+ require 'traits/concerns/attribute/type'
12
+ require 'traits/concerns/attribute/essay_shortcuts'
13
+
14
+ module Traits
15
+ class Attribute
16
+ include Key
17
+ include Naming
18
+ include Type
19
+ include Polymorphism
20
+ include Querying
21
+ include STI
22
+ include EssayShortcuts
23
+
24
+ attr_reader :model_class, :column_definition
25
+
26
+ def initialize(model_class, column_definition)
27
+ @model_class = model_class
28
+ @column_definition = column_definition
29
+ end
30
+
31
+ def model
32
+ model_class.traits
33
+ end
34
+
35
+ # TODO Remove this
36
+ def regular?
37
+ !(key? || sti_type? || polymorphic_type? || active_record_timestamp?)
38
+ end
39
+
40
+ def validators
41
+ model_class.validators_on(name)
42
+ end
43
+
44
+ def value_from(model_instance)
45
+ model_instance[name]
46
+ end
47
+
48
+ def to_s
49
+ "#{model}##{name}"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,7 @@
1
+ module Traits
2
+ class Base
3
+ def to_hash
4
+ {}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Traits
2
+ class Association
3
+ module EssayShortcuts
4
+ def features
5
+ from_class.association_features[name]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,55 @@
1
+ module Traits
2
+ class Association
3
+ module Intermediate
4
+ # class Person
5
+ # has_and_belongs_to_many :groups
6
+ #
7
+ # person_traits.associations[:groups].intermediate? => true
8
+ #
9
+ def intermediate?
10
+ has_and_belongs_to_many?
11
+ end
12
+
13
+ def intermediate_table
14
+ if intermediate?
15
+ Arel::Table.new(reflection.join_table)
16
+ end
17
+ end
18
+
19
+ def intermediate_table_name
20
+ if intermediate?
21
+ reflection.join_table
22
+ end
23
+ end
24
+
25
+ def intermediate_to_key
26
+ intermediate_table.try(:[], intermediate_to_key_name)
27
+ end
28
+
29
+ def intermediate_to_key_name
30
+ if intermediate?
31
+ reflection.foreign_key.to_sym
32
+ end
33
+ end
34
+
35
+ def intermediate_from_key
36
+ intermediate_table.try(:[], intermediate_from_key_name)
37
+ end
38
+
39
+ def intermediate_from_key_name
40
+ if intermediate?
41
+ reflection.association_foreign_key.to_sym
42
+ end
43
+ end
44
+
45
+ def to_hash
46
+ super.merge!(
47
+ intermediate: intermediate?,
48
+ intermediate_table_name: intermediate_table_name,
49
+ intermediate_to_key_name: intermediate_to_key_name,
50
+ intermediate_from_key_name: intermediate_from_key_name
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,89 @@
1
+ module Traits
2
+ class Association
3
+ module Join
4
+ def from_table
5
+ from.arel
6
+ end
7
+
8
+ def from_table_name
9
+ from_table.name.to_sym
10
+ end
11
+
12
+ def from_table_alias
13
+ from_table.table_alias.try(:to_sym)
14
+ end
15
+
16
+ def from_key
17
+ from_table[from_key_name]
18
+ end
19
+
20
+ def from_key_name
21
+ if polymorphic?
22
+ reflection.foreign_key.to_sym
23
+
24
+ elsif through?
25
+ through_association.from_key_name
26
+
27
+ elsif features.translates_with_globalize?
28
+ nil
29
+
30
+ elsif belongs_to?
31
+ reflection.foreign_key.to_sym
32
+
33
+ end || reflection.active_record_primary_key.to_sym
34
+ end
35
+
36
+ def to_table
37
+ unless polymorphic?
38
+ table = to.arel.clone
39
+ if self_to_self?
40
+ table.table_alias = "#{plural_name}_#{from.table_name}"
41
+ end
42
+ table
43
+ end
44
+ end
45
+
46
+ def to_table_name
47
+ unless polymorphic?
48
+ to_table.name.to_sym
49
+ end
50
+ end
51
+
52
+ def to_table_alias
53
+ unless polymorphic?
54
+ to_table.table_alias.try(:to_sym)
55
+ end
56
+ end
57
+
58
+ def to_key
59
+ to_table.try(:[], to_key_name)
60
+ end
61
+
62
+ def to_key_name
63
+ unless polymorphic?
64
+ if through?
65
+ source_association.to_key_name
66
+
67
+ elsif belongs_to? || has_and_belongs_to_many?
68
+ reflection.association_primary_key.to_sym
69
+
70
+ else
71
+ reflection.foreign_key.to_sym
72
+ end
73
+ end
74
+ end
75
+
76
+ def to_hash
77
+ super.merge!(
78
+ from_table_name: from_table_name,
79
+ from_table_alias: from_table_alias,
80
+ from_key_name: from_key_name,
81
+
82
+ to_table_name: to_table_name,
83
+ to_table_alias: to_table_alias,
84
+ to_key_name: to_key_name
85
+ )
86
+ end
87
+ end
88
+ end
89
+ end