Reputation: 697
I think it's not an uncommon requirement, but I can't find a proper solution.
I have a recipe model with a n:1 Relation to instructions.
class Recipe < ActiveRecord::Base
has_many :instructions, autosave: true, class_name: 'RecipeInstruction'
end
No I want to order the instructions in a recipe by hand. So my first approach was to add a position attribute to instructions and add the following.
class Recipe < ActiveRecord::Base
has_many :instructions, -> { order('position ASC') }, autosave: true, class_name: 'RecipeInstruction'
end
I set the position attribute in the recipe controller. There are two disadvantages with this solution:
In other programming languages I would override getter and setter of this relation. Which Operators of the association I have to override to cover all possibilities of adding an object to the relation?
PS: I do know that I can override the << operator of the association with:
has_many :instructions, -> { order('position ASC') }, autosave: true, class_name: 'RecipeInstruction' do
def << *args
end
end
But the = Operator can't be overwritten this way, can it?
Edit: Now I know how to override the = Operator. Do I have to override all possibilities or is there a method that is called by every operator to add an instruction, like push? And how can I force a "reload" of the related objects when I change the position attribute of one or more instructions?
PPS: Overriding the setter for instructions I tried both variants, but none was called by rails after submitting the form. But the instructions where assigned to the recipe. So there must be another option so set it/to override the setter:
has_many :instructions, -> { order('position ASC') }, autosave: true, class_name: 'RecipeInstruction' do
def instructions=(instructions)
raise error
end
end
has_many :instructions, -> { order('position ASC') }, autosave: true, class_name: 'RecipeInstruction'
def instructions=(instructions)
raise error
end
PPPS: Solution for setting the position
The first step is taken. I think this is a good solution for initializing the position attribute:
has_many :instructions, -> { order('position ASC') }, autosave: true, class_name: 'RecipeInstruction', before_add: :initialize_instruction
def initialize_instruction(instruction)
instruction.position = instructions.length
end
Upvotes: 3
Views: 217
Reputation: 697
Thanks to all your help. I finally found a good solution:
https://github.com/swanandp/acts_as_list
It is a plugin which solves the problem very well.
Edit: But only for persisted records. I assume that is 'active record style'.
Upvotes: 1
Reputation: 76774
I think you have the answer already; to give something which may help, you have access to the proxy_association
objects when you extend the has_many
association:
has_many :instructions, -> { order('position ASC') }, autosave: true, class_name: 'RecipeInstruction' do
def x
proxy_association.target
end
end
According to these docs, you'll also get access to record.association(:name)
objects:
@recipe = Recipe.find x
@recipe.instructions = @recipe.association(:instructions).target #-> returns collection of instructions
This will give you access to an array of the instructions, from which you'll be able to extract the positions
:
@positions = @recipe.association(:instructions).target.select { |k,v| key.to_s.match(/^position\d+/) }
There's also a reload
method:
person.pets.reload # fetches pets from the database
# => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
Sadly, I don't have anything for interjecting into the <<
/ .destroy
methods.
Upvotes: 1
Reputation: 1258
You can write getters/setters in Rails. For example, to write a getter and setter for your Recipe object (in your model),
def position
where('some req').order('some way')
end
def position=(arg1)
self.something = arg1
end
That's how you override the =
operator.
Upvotes: 0