prim 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +74 -0
- data/lib/prim.rb +2 -1
- data/lib/prim/collection.rb +15 -0
- data/lib/prim/instance_methods.rb +7 -5
- data/lib/prim/relationship.rb +13 -54
- data/prim.gemspec +3 -1
- metadata +19 -4
- data/lib/prim/primary.rb +0 -11
- data/lib/tasks/prim.rake +0 -0
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
Overview
|
2
|
+
--------
|
3
|
+
|
4
|
+
__Prim__ gives you the power to assign and manage the primary member of any Rails one–to–many or many–to–many assocation. And it's really simple to get started! Let's say we needed to model a User who could have several Languages:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
class User < ActiveRecord::Base
|
8
|
+
has_many :user_languages
|
9
|
+
has_many :languages, through: :user_languages
|
10
|
+
|
11
|
+
...
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class UserLanguage < ActiveRecord::Base
|
17
|
+
belongs_to :user
|
18
|
+
belongs_to :language
|
19
|
+
|
20
|
+
...
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class Language < ActiveRecord::Base
|
26
|
+
has_many :user_languages
|
27
|
+
has_many :users, through: :user_languages
|
28
|
+
|
29
|
+
...
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
Great! Now let's say we've added a specific set of Languages. Our Users can now have new Language associations by simply creating a record in the `user_languages` mapping table, relating a User and a Language. But what if we want to know which of a User's Languages is their most important? Well, we could add a `sort_order` or `primary` column to the `user_languages` table, but then we'll need to write code to manage it all.
|
34
|
+
|
35
|
+
Enter __Prim__.
|
36
|
+
|
37
|
+
With __Prim__ we can just add a line of code to the User model...
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class User < ActiveRecord::Base
|
41
|
+
...
|
42
|
+
|
43
|
+
has_primary :language
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
...and run a migration:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class AddPrimaryToUserLanguages < ActiveRecord::Migration
|
51
|
+
def change
|
52
|
+
add_column :user_languages, :primary, :boolean, { default: false }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
And we're done! Now we can set any User's primary language...
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
User.first.primary_language = Language.where( name: "English" )
|
61
|
+
```
|
62
|
+
|
63
|
+
...and retrieve it:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
User.first.primary_language
|
67
|
+
=> #<Language id: 5, name: "English" ... >
|
68
|
+
```
|
69
|
+
|
70
|
+
Contributing
|
71
|
+
------------
|
72
|
+
|
73
|
+
Want to contribute? Find a TODO or [Github issue](https://github.com/OrcaHealth/prim/issues) and take care of it! Or suggest a feature or file a bug on the [issues page](https://github.com/OrcaHealth/prim/issues). Just pack up your commits by rebasing them, then submit a pull request!
|
74
|
+
|
data/lib/prim.rb
CHANGED
@@ -15,6 +15,7 @@ module Prim
|
|
15
15
|
module ClassMethods
|
16
16
|
include Prim::Helpers
|
17
17
|
|
18
|
+
# TODO: allow multiple singular names in one call.
|
18
19
|
def has_primary name, options = {}
|
19
20
|
singular_name = name.to_sym
|
20
21
|
association_name = plural_sym(singular_name)
|
@@ -36,5 +37,5 @@ module Prim
|
|
36
37
|
end
|
37
38
|
|
38
39
|
class SingularAssociationError < StandardError; end
|
39
|
-
class
|
40
|
+
class InvalidPrimaryColumnError < StandardError; end
|
40
41
|
end
|
data/lib/prim/collection.rb
CHANGED
@@ -13,6 +13,9 @@ module Prim
|
|
13
13
|
def initialize relationship, instance
|
14
14
|
@instance = instance
|
15
15
|
@relationship = relationship
|
16
|
+
|
17
|
+
# Attach this collection to the mapping class so it has access to static methods.
|
18
|
+
relationship.reflected_class.prim_collection = self
|
16
19
|
end
|
17
20
|
|
18
21
|
def primary
|
@@ -37,6 +40,18 @@ module Prim
|
|
37
40
|
true
|
38
41
|
end
|
39
42
|
|
43
|
+
def siblings_for mapping
|
44
|
+
foreign_key = relationship.mapping_reflection.foreign_key
|
45
|
+
mapping_type = relationship.mapping_reflection.type
|
46
|
+
mapping_class = relationship.reflected_class
|
47
|
+
primary_key = mapping_class.primary_key
|
48
|
+
|
49
|
+
query = relationship.reflected_class.where( foreign_key => mapping[ foreign_key ] )
|
50
|
+
query = query.where( mapping_type => mapping[ mapping_type ] ) unless mapping_type.nil?
|
51
|
+
|
52
|
+
query.where( mapping_class.arel_table[ primary_key ].not_eq( mapping[ primary_key ] ) )
|
53
|
+
end
|
54
|
+
|
40
55
|
private
|
41
56
|
|
42
57
|
# Creates a new source record and a mapping between it and the owner instance.
|
@@ -23,15 +23,17 @@ module Prim
|
|
23
23
|
extend ActiveSupport::Concern
|
24
24
|
|
25
25
|
included do
|
26
|
-
|
27
26
|
validate :only_one_primary
|
27
|
+
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
29
|
+
def only_one_primary
|
30
|
+
if self[:primary]
|
31
|
+
siblings.update_all('"primary" = false')
|
33
32
|
end
|
33
|
+
end
|
34
34
|
|
35
|
+
def siblings
|
36
|
+
prim_collection.siblings_for self
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
data/lib/prim/relationship.rb
CHANGED
@@ -9,52 +9,32 @@ module Prim
|
|
9
9
|
delegate :source_reflection, :through_reflection, :foreign_key, to: :reflection
|
10
10
|
|
11
11
|
def initialize association_name, owning_class, options = {}
|
12
|
-
@options =
|
12
|
+
@options = options
|
13
13
|
@association_name = association_name
|
14
14
|
@owning_class = owning_class
|
15
15
|
@reflection = owning_class.reflect_on_association( association_name )
|
16
16
|
|
17
|
+
# TODO: remove these exceptions and replace with logged errors? hmm.
|
17
18
|
if reflection.nil?
|
18
|
-
raise ArgumentError.new("
|
19
|
+
raise ArgumentError.new("Association '#{ association_name }' not found " +
|
19
20
|
"on #{ owning_class.name }. Perhaps you misspelled it?")
|
20
21
|
|
21
22
|
elsif !reflection.collection?
|
22
|
-
raise SingularAssociationError.new("
|
23
|
+
raise SingularAssociationError.new("Association '#{ association_name }' " +
|
23
24
|
"is not a one-to-many or many-to-many relationship, so it can't have a primary.")
|
24
25
|
|
25
|
-
elsif !
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
elsif !primary_column
|
27
|
+
# TODO: add a generator to automatically create a migration, then change this
|
28
|
+
# message to give users the exact instruction needed.
|
29
|
+
raise InvalidPrimaryColumnError.new("#{ owning_class.name } needs table " +
|
30
|
+
"`#{ mapping_reflection.table_name }` to have a boolean 'primary' column " +
|
31
|
+
"in order to have a primary #{ source_class.name }.")
|
29
32
|
end
|
30
33
|
|
31
34
|
# TODO: ensure the association isn't nested?
|
32
35
|
|
33
|
-
configure_reflected_class!
|
34
|
-
|
35
|
-
true
|
36
|
-
end
|
37
|
-
|
38
|
-
def configure_reflected_class!
|
39
|
-
foreign_key = mapping_reflection.foreign_key
|
40
|
-
mapping_type = mapping_reflection.type
|
41
|
-
|
42
|
-
load_siblings = lambda do
|
43
|
-
primary_key = self.class.primary_key
|
44
|
-
query = self.class.where( foreign_key => self[ foreign_key ] )
|
45
|
-
|
46
|
-
if mapping_type
|
47
|
-
query = query.where( mapping_type => self[ mapping_type ] )
|
48
|
-
end
|
49
|
-
|
50
|
-
query.where( self.class.arel_table[ primary_key ].not_eq(self[ primary_key ]) )
|
51
|
-
end
|
52
|
-
|
53
|
-
reflected_class.class_eval do
|
54
|
-
define_method :siblings, &load_siblings
|
55
|
-
end
|
56
|
-
|
57
36
|
reflected_class.send :include, InstanceMethods::Reflected
|
37
|
+
reflected_class.class_attribute :prim_collection
|
58
38
|
end
|
59
39
|
|
60
40
|
# The association method to call on the owning class to retrieve a record's collection.
|
@@ -78,22 +58,6 @@ module Prim
|
|
78
58
|
through_reflection || source_reflection
|
79
59
|
end
|
80
60
|
|
81
|
-
# True if the `mapping_reflection` class has an "inverse" mapping back to the owning
|
82
|
-
# class with a matching name. Verifies that a polymorphic mapping exists.
|
83
|
-
# def polymorphic_mapping?
|
84
|
-
# if polymorphic_as.present?
|
85
|
-
# !!reflected_class.reflect_on_all_associations.detect do |refl|
|
86
|
-
# refl.name == polymorphic_as and refl.association_class == ActiveRecord::Associations::BelongsToPolymorphicAssociation
|
87
|
-
# end
|
88
|
-
# end
|
89
|
-
# end
|
90
|
-
|
91
|
-
# The name the owning class uses to create mappings in the reflected class; i.e. the
|
92
|
-
# `:as` option set on the `has_many` association in the owner.
|
93
|
-
# def reflection_polymorphic_as
|
94
|
-
# mapping_reflection.options[:as].to_sym
|
95
|
-
# end
|
96
|
-
|
97
61
|
# True if this relationship relies on a mapping table for `primary` records.
|
98
62
|
def mapping_table?
|
99
63
|
!!through_reflection
|
@@ -101,13 +65,8 @@ module Prim
|
|
101
65
|
|
102
66
|
private
|
103
67
|
|
104
|
-
|
105
|
-
|
106
|
-
reflected_class.column_names
|
107
|
-
end
|
108
|
-
|
109
|
-
def extract_options options
|
110
|
-
options
|
68
|
+
def primary_column
|
69
|
+
reflected_class.columns.find { |col| col.name == "primary" }
|
111
70
|
end
|
112
71
|
end
|
113
72
|
end
|
data/prim.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "prim"
|
3
|
-
s.version = "0.0.
|
3
|
+
s.version = "0.0.3"
|
4
4
|
s.date = "2012-12-19"
|
5
5
|
s.summary = "Easily manage Rails associations that need a primary member."
|
6
6
|
s.description = "With Prim it's easy to add a primary member to any one-to-many or many-to-many association. " +
|
@@ -10,5 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.files = `git ls-files`.split("\n")
|
11
11
|
s.homepage = "https://github.com/orcahealth/prim"
|
12
12
|
|
13
|
+
s.add_dependency "activerecord", "~> 3.2.0"
|
14
|
+
|
13
15
|
s.require_paths = [ "lib" ]
|
14
16
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prim
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,23 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
date: 2012-12-19 00:00:00.000000000 Z
|
13
|
-
dependencies:
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
prerelease: false
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.0
|
22
|
+
none: false
|
23
|
+
type: :runtime
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 3.2.0
|
29
|
+
none: false
|
14
30
|
description: With Prim it's easy to add a primary member to any one-to-many or many-to-many
|
15
31
|
association. Just add a short configuration to a model, generate and run a migration,
|
16
32
|
and you're all set.
|
@@ -19,15 +35,14 @@ executables: []
|
|
19
35
|
extensions: []
|
20
36
|
extra_rdoc_files: []
|
21
37
|
files:
|
38
|
+
- README.md
|
22
39
|
- lib/prim.rb
|
23
40
|
- lib/prim/collection.rb
|
24
41
|
- lib/prim/connector.rb
|
25
42
|
- lib/prim/helpers.rb
|
26
43
|
- lib/prim/instance_methods.rb
|
27
|
-
- lib/prim/primary.rb
|
28
44
|
- lib/prim/railtie.rb
|
29
45
|
- lib/prim/relationship.rb
|
30
|
-
- lib/tasks/prim.rake
|
31
46
|
- prim.gemspec
|
32
47
|
homepage: https://github.com/orcahealth/prim
|
33
48
|
licenses: []
|
data/lib/prim/primary.rb
DELETED
data/lib/tasks/prim.rake
DELETED
File without changes
|