Hi I have 6 models with relations with each other:
Spell model which can have many tags and elements and one ring(aka level)
class Spell < ActiveRecord::Base
belongs_to :spell_ring
has_many :element_of_spells, dependent: :destroy
has_many :spell_elements, through: :element_of_spells
has_many :tag_of_spells, dependent: :destroy
has_many :spell_tags, through: :tag_of_spells
validates_presence_of :name
end
Spell element which can have many spells
class SpellElement < ActiveRecord::Base
has_many :element_of_spells, dependent: :destroy
has_many :spells, through: :element_of_spells
end
Spell tag, which can have many spells:
class SpellTag < ActiveRecord::Base
has_many :tag_of_spells, dependent: :destroy
has_many :spells, through: :tag_of_spells
end
Spell ring:
class SpellRing < ActiveRecord::Base
has_many :spells
end
And join models:
class ElementOfSpell < ActiveRecord::Base
belongs_to :spell
belongs_to :spell_element
end
class TagOfSpell < ActiveRecord::Base
belongs_to :spell
belongs_to :spell_tag
end
Ok now I want to put them to good use :)
What I know:
That if I take any spell_tag or spell_element or spell_ring object, I can get all associated spells.
element = SpellElement.first
spells_of_element = element.spells >> give me all associated spells
I know I can scope this with spell_ring_id
since it is part of the spell object.
spell_ring = SpellRing.first
spells_of_element_and_ring = spells_of_element.where( spell_ring_id: spell_ring.id ) >> returns spells of given element and ring
What I don`t know:
How to scope spells_of_element_and_ring
with given tag.
tag = SpellTag.first
spells_of_element_ring_and_tag = ??
Updated
What I want?
Is to be able to query spells:
and any combination of those three models.
It's a good idea, when posting to StackOverflow, to weed out as much code as possible, and really boil your question down to the simplest possible example. This will get quicker answers, and also be useful to more people.
Let's start out with the simpler example of a school, which has many classrooms, and each classroom has many students.
Let's create our models:
rails generate model school name:string
rails generate model classroom school_id:integer grade:integer
rails generate model student name:string classroom_id:integer
Now let's create our associations:
class School < ActiveRecord::Base
has_many :classrooms
has_many :students, through: :classrooms
end
class Classroom < ActiveRecord::Base
belongs_to :school
has_many :students
end
class Student < ActiveRecord::Base
belongs_to :classroom
end
Finally, we'll create three quick records:
school = School.create name: 'City Elementary'
classroom = school.classrooms.create grade: 4
student = classroom.students.create name: 'Bob'
Now we can get a list of all students at the school like so:
school.students
This works because a school has_many
students, through
classrooms.
I think what you actually want is a little more complicated - a spell can have many elements, and an element can belong to many spells. In this case, you need a "join table". Let's simplify your example by eliminating everything except spells and elements.
We start by creating our models:
rails generate model spell name:string
rails generate model element name:string
Now we create a join table, which keeps track of which spells and elements belong to each other:
rails generate migration create_elements_spells element_id:integer spell_id:integer
Now we define our associations (relationships):
class Element < ActiveRecord::Base
has_and_belongs_to_many :spells
end
class Spell < ActiveRecord::Base
has_and_belongs_to_many :elements
end
has_and_belongs_to_many
automatically looks for a table with the combined name of the two models, in plural form, in alphabetical order. Now we can do things like:
spell = Spell.create name: 'set on fire'
flint = Element.create name: 'flint'
steel = Element.create name: 'steel'
spell.elements << flint
spell.elements << steel
Now, spell.elements
lists both flint and steel. flint.spells
will list our 'set on fire' spell. steel.spells
will also list our spell. You can expand from there.
But what if you need to know more than just what element - what if you need to know how much? now you have extra data that doesn't belong in the Spell record or the Element record. It belongs on the association itself. We might call an element/amount combo an "ingredient", and create a table for it like so:
rails generate model ingredient spell_id:integer element_id:integer amount:string
And we update our associations:
class Element < ActiveRecord::Base
has_many :ingredients
has_many :spells, through: :ingredients
end
class Spell < ActiveRecord::Base
has_many :ingredients
has_many :elements, through: :ingredients
end
class Ingredient < ActiveRecord::Base
belongs_to :element
belongs_to :spell
end
Now we can add ingredients to our spell:
spell.ingredients.create element: flint, amount: '1 gram'
spell.ingredients.create element: steel, amount: '1 piece'
So spell.ingredients
will list both flint and steel, and the amounts for each. This should get you well on your way to building you application.
@Jaime explain querying through the whole process very good. But I wanted my query to be more flexible.
I don't know it this is the Rails way. But I found something like this to fit me the best.
Because SpellElement.spells, SpellRing.spells, SpellTag.spells all returns an array. My idea is to just compare them and return only matched elements as a result.
So
spell_element_ring_and_tag = some_spell_element.spells & some_spell_ring.spells & some_spell_tag.spells
Will return only spell objects shared by all three arrays.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.