activerecord-traits 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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