Rails append type to join table with has_many :through and association extensions - ruby

I have an AR association with extensions in Rails similar to the example presented in this link:
ActiveRecord Association Extensions
has_many :issues, :through => :qbert_issues do
def tracking
where("qbert_issues.kind = ?", "tracking")
end
def blocking
where("qbert_issues.kind = ?", "blocking")
end
end
As shown above, mine is multi-typed... I need to populate a 'kind' column in my join table. Ideally, this should just work:
q = QBert.find(123)
q.issues.tracking << Issue.find(234)
So, what the article suggests is overloading << and doing something like this:
has_many :issues, ... do
...
def <<(issue)
issue.kind = "UserAccount"
proxy_association.owner.issues += [issue]
end
end
Which would be nice, if kind was static.
It looks like I can do this...
has_many :issues, ... do
...
def <<(*args)
issue, kind = args.flatten
issue.kind = kind
proxy_association.owner.issues += [issue]
end
end
Which would allow me to do this at the very least:
q = QBert.find(123)
q.issues.tracking << [Issue.find(234), :tracking]
That doesn't seem very DRY to me...is there a better way? Bonus points if you take into account that the kind accessor is off a join table qbert_issues. I'm guessing I just have to add the association manually through the QBertIssue model directly.

Figured it out...
def <<(issue)
kind = where_values.second.scan(/kind = '(.*)'/).flatten.first
left = proxy_association.reflection.source_reflection
right = proxy_association.reflection.through_reflection
left.active_record.create(left.foreign_key.to_sym => issue.id,
right.foreign_key.to_sym => proxy_association.owner.id,
:kind => kind)
end
Which lets me do:
q = QBert.find(123)
q.issues.tracking << Issue.find(234)
It could be made sufficiently generalized by parsing out the where_values and merging them into the parameters hash.
Pry rocks, by the way :D

Related

rails3 custom validation overlapping dates error

Building a validator that has to check multiple siblings who belong to the same (option) parent.
class Optionrate < ActiveRecord::Base
belongs_to :option
attr_accessible :from, :to, :option_id
validates_presence_of :from, :to
validate :not_overlap
scope :overlaps, ->(from, to) do
where "((from <= ?) and (to >= ?))", to, from
end
def overlaps?
overlaps.exists?
end
def overlaps
siblings.overlaps from, to
end
def not_overlap
errors.add(:key, t('overlap_message')) if overlaps?
end
def siblings
Optionrate.where('option_id = ?', option_id).all
end
is generating an error: "undefined method `overlaps' for []:Array" referring to statement
siblings.overlaps from, to
The fact that siblings is plural makes me assume it is expecting an array, so that's an oddity.
[Another was that the where statement was not accepting *where('option_id = ?', params[:option_id])* whence the record has yet to be created as the validation has not completed]
Please try to run the code after removing .all from Optionrate.where('option_id = ?', option_id).all because when you are using .Where then there is no need to use .all method.
Or
Take a look on following url for reference
http://guides.rubyonrails.org/3_2_release_notes.html#active-record

Rails 3. Decide on save if the object should be saved or not

iam just asking myself, whats the best solution for my problem.
Here are my models:
class Product < ActiveRecord::Base
has_many :prices, :class_name => "ProductPrice"
accepts_nested_attributes_for :prices
end
class ProductPrice < ActiveRecord::Base
belongs_to :product
end
The controller
def create
#product = Product.new(params[:product])
#product.save
...
end
What i want to do is to prevent all ProductPrices from being saved when product_price.value == nil or product_price.value == 0.0
before_save hook in ProductPrice. return false will rollback the whole transaction, thats not what i want to do. i just want to "kick" all prices with value == 0 or value == nil
first kick all price_params from params[...] and than call Product.new(params[:product]) seems not to be the rails way eighter...
after Product.new(params[:product]) iterate over all prices and delete them from the array. but the logic should be in my models right? i just dont want to repeat myself on every controller that creates new prices...
can someone tell me the best solution for that? whats the rails way?
thanks!
What you want it called a validation hook, something like this:
class ProductPrice < ActiveRecord::Base
belongs_to :product
validates :value, :numericality => {:greater_than => 0.0 }
end
See http://guides.rubyonrails.org/active_record_validations_callbacks.html for other ways you may want to do this with finer control.
To avoid adding these invalid prices in the first place, you can remove them from the nested attributes hash like this:
class Product < ActiveRecord::Base
def self.clean_attributes!(product_params)
product_prices = product_params['prices'] || []
product_prices.reject!{|price| price['value'].to_f == 0 rescue true }
end
end
Product.clean_attributes!(params[:product])
Product.new(params[:product])

Rails nested form on many-to-many: how to prevent duplicates?

I've setup a nested form in my rails 3.2.3 app, it's working fine, my models are:
class Recipe < ActiveRecord::Base
attr_accessible :title, :description, :excerpt, :date, :ingredient_lines_attributes
has_and_belongs_to_many :ingredient_lines
accepts_nested_attributes_for :ingredient_lines
end
and:
class IngredientLine < ActiveRecord::Base
attr_accessible :ingredient_id, :measurement_unit_id, :quantity
has_and_belongs_to_many :recipes
belongs_to :measurement_unit
belongs_to :ingredient
end
As above, a Recipe can have multiple IngredientLines and vice versa.
What I'm trying to avoid is record duplication on IngredienLine table.
For example imagine that for recipe_1 an IngredientLine with {"measurement_unit_id" => 1, "ingredient_id" => 1, "quantity" => 3.5} is associated, if for recipe_5 the IngredientLine child form is compiled by the user with the same values, I don't want a new record on IngredientLine table, but only a new association record in the join table ingredient_lines_recipes.
Note that currently I dont't have any IngredientLine controller as saving and updating IngredientLines is handled by nested form routines. Even my Recipe controller is plain and standard:
class RecipesController < ApplicationController
respond_to :html
def new
#recipe = Recipe.new
end
def create
#recipe = Recipe.new(params[:recipe])
flash[:notice] = 'Recipe saved.' if #recipe.save
respond_with(#recipe)
end
def destroy
#recipe = Recipe.find(params[:id])
#recipe.destroy
respond_with(:recipes)
end
def edit
respond_with(#recipe = Recipe.find(params[:id]))
end
def update
#recipe = Recipe.find(params[:id])
flash[:notice] = 'Recipe updated.' if #recipe.update_attributes(params[:recipe])
respond_with(#recipe)
end
end
My guess is that should be enough to override the standard create behavior for IngredientLine with find_or_create, but I don't know how to achieve it.
But there's another important point to take care, imagine the edit of a child form where some IngredientLines are present, if I add another IngredientLine, which is already stored in IngredientLine table, rails of course should not write anything on IngredientLine table, but should also distinguish between child records already associated to the parent, and the new child record for which needs to create the relation, writing a new record on the join table.
Thanks!
in Recipe model redefine method
def ingredient_lines_attributes=(attributes)
self.ingredient_lines << IngredientLine.where(attributes).first_or_initialize
end
Old question but I had the same problem. Forgot to add :id to white list with rails 4 strong_parameters.
For example:
widgets_controller.rb
def widget_params
params.require(:widget).permit(:name, :foos_attributes => [:id, :name, :_destroy],)
end
widget.rb
class Widget < ActiveRecord::Base
has_many :foos, dependent: :destroy
accepts_nested_attributes_for :foos, allow_destroy: true
end
foo.rb
class Foo < ActiveRecord::Base
belongs_to :widget
end
I have run into a similar situation and found inspiration in this answer. In short, I don't worry about the duplication of nested models until save time.
Translated to your example, I added autosave_associated_records_for_ingredient_lines to Recipe. It iterates through ingredient_lines and performs a find_or_create as your intuition said. If ingredient_lines are complex, Yuri's first_or_initialize approach may be cleaner.
I believe this has the behavior you're looking for: nested models are never duplicated, but editing one causes a new record rather than updating a shared one. There is the strong possibility of orphaned ingredient_lines but if that's a serious concern you could choose to update if that model has only one recipe with an id that matches the current one.

Create association before record is saved

So, I'm admittedly a Rails newbie, and I'm running into what must be a fairly common issue, but I can't find the answer here.
I have a model Foo like this:
class Foo < ActiveRecord::Base
has_many :bars
end
Bars belongs_to Foo, all that works. Now I want to create a Foo and build Bar at the same time. Like this:
f = Foo.new(:baz => 'baz')
bars.each do |b|
f.bars.build(:bizzy => b[:bizzy])
end
f.save
I know this won't work because the parent record doesn't exist, so the association doesn't exist, but there must be a way to do this. I've temporarily gotten around it by editing to this:
f = Foo.new(:baz => 'baz')
f.save
f = Foo.find(:first, :conditions => {:baz => 'baz'})
bars.each do |b|
f.bars.create(:bizzy => b[:bizzy])
end
But that is not clean, and is all around unpleasant.
What is the right way to do this?
In the first line you can just use create instead of new. You don't need f.bars.create or f.bars.build, because the bar object already exists. I would do this:
f = Foo.create(:baz => 'baz')
bars.each do |b|
f.bars << b
end
Personally I wouldn't iterate over the bars, but just use update_all:
f = Foo.create(:baz => 'baz')
bars.update_all(:foo_id => f.id)
Edit: it's possible to do this without saving the record first. This works for me:
f = Foo.new(:baz => 'baz')
bars.each do |b|
f.bars << b
end
f.save

Best way to iterate over a sequence and change one object at a time (Ruby)

I'm new to ruby and feel that I still do a lot of things in C sharpish way :-).
Suppose you have an array of objects (Question :has_many=>:answers).
I want to iterate over all answers and if some answers meet a criteria, change answer attribute.
Currently I'm doing it as follows:
def iterate_and_do_stuff
for (a in self.answers)
if(a.somecriteria==true)
a.some_attr=some_val
end
end
end
What are other ways of doing this? Blocks, loops, etc?
Please adivce.
Thank you.
Use Array#each:
self.answers.each {|a| a.some_attr = some_val if a.some_criteria}
I prefer map! or collect! in this case, because you can use the ! method to semantically represent you are changing the array in place.
self.answers.map!{ |a|
a.some_criteria ? (a.some_attr = some_val) : a.some_attr
}
or
self.answers.collect!{ |a|
a.some_criteria ? (a.some_attr = some_val) : a.some_attr
}
This way it is very clear that you intend to change the array.
self.answers.select{|a| a.somecriteria}.each{|a| a.some_attr = some_val}
OR
self.answers.find_all{|a| a.somecriteria}.each{|a| a.some_attr = some_val}
Both are less efficient than your original code, though.
Just for fun an alternative
self.answers.select{|a| a.some_criteria}.each{|a| a.some_attr = some_val}
If you're working with ActiveRecord models, don't forget that you can do your selection at the database level, rather than in memory. To rephrase, you can retrieve from the database only the values that you want into your array, and then simply change those.
Here's an example using custom finders (sort of an older way to do it):
class Question < ActiveRecord::Base
has_many :answers do
def accepted
find :all, :conditions => { :accepted => true }
end
end
end
class Answer < ActiveRecord::Base
belongs_to :question
end
q = Question.find :first
q.answers.accepted.each { |a| a.do_something! }
Or you could do it with another association:
class Question < ActiveRecord::Base
has_many :answers
has_many :accepted_answers, :class_name => "Answer", :conditions => { :accepted => true }
end
class Answer < ActiveRecord::Base
belongs_to :question
end
q = Question.find :first
q.accepted_answers.each { |a| a.do_something! }
Here's another example using named scopes (a little newer, and preferred in my opinion) on your child class:
class Question < ActiveRecord::Base
has_many :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
named_scope :accepted, :conditions => { :accepted => true }
end
q = Question.find :first
q.answers.accepted.each { |a| a.do_something! }
But in any of the cases, you've abstracted the "selection", which has a few benefits:
Faster in cases of large collections
You've abstracted the lower level selection criteria to something with higher level semantic meaning, which makes your code easier to read and maintain

Resources