I used rich many to many...
class OwnerRepository < Hanami::Repository
associations do
has_one :avator
has_many :oneships#It's for join table..
has_many :avators,through: :oneships
end
def find_with_avator(id)
aggregate(:avator).where(id: id).map_to(Owner).one
end
def find_with_avators(id)
aggregate(:avators).where(id: id).map_to(Owner).one
#This found one avator but should be found two???
end
end
I create two owners and two avator for each owner,And every owner has two avators with through oneship.
But when i want to find avators with given owner_id,Give me one result.
In other side Avator Repository works fine..
Why?
I want to have a setup where nothing is allowed to alter database state. Then I want to be able incrementally white-list allowed operations.
Extend activerecord base.
class MyAuthoritarianRecord < ActiveRecord::Base
belongs_to :user
# crazy logic here
def destroy
if user.wont_submit_to_bondage_and_discipline?
# silently fail
else
super
end
end
end
class PropertyOfTheState < MyAuthoritarianRecord
end
I have an Accounts model, which have many CreditCards and has one BillingInfo.
In CreditCards controller I initialize with the help of CollectionProxy:
class CreditCardsController < ApplicationController
def create
credit_card = current_account.credit_cards.new(credit_card_params)
...
end
end
However, this doesn't work with has_one association:
class BillingInfosController < ApplicationController
def create
billing_info = current_account.billing_info.new(billing_info_params)
...
end
end
The reason is; calling billing_info on current_account does return nil rather than empty CollectionProxy, which results sending new on nil and exists with NoMethodError.
Is there a way to use CollectionProxy or something similar to keep using
current_account.billing_info.new(billing_info_params)
rather than something like
BillingInfo.new(billing_info_params.merge(account_id: current_account.id))
to initialize? Thanks in advance!
You should be able to use current_account.build_billing_info or current_account.create_billing_info which are methods added by the has_one association.
When initializing a new has_one or belongs_to association you must use the build_ prefix to build the association, rather than the association.build method that would be used for has_many or has_and_belongs_to_many associations. To create one, use the create_ prefix.
See the has_one association reference for more about these methods and the other methods active record adds for you.
one solution is ensure every account has_one billing_info
you can user after_create callback to create a account's billing_info
another one is get billing_info first
billing_info = current_account.billing_info || current_account.build_billing_info
billing_info.assign_attributes(billing_info_params)
I have three models. Employer, User, Job.
class Employers
has_many :jobs
has_many :users, through: :jobs
end
class User
has_many :jobs
end
class Job
belongs_to :user
belongs_to :employer
end
The Job model has a boolean column named "current". An employers user count is derived by counting all the associated jobs marked 'current'.
I opted to rolled my own cache counter, rather than use active records.
Im using a before filter in the Job model to either increment or decrement a users_count in the Employer model. The increment works as expected, but no matter how I tweak the code...the decrement drops the count by a value of 2.
Im sure I can clean these methods up a bit...there might be some redundancy.
1 Why is the decrement subtracting 2 instead of 1?
2 Can the active record cache counter handle logic like this?
class Job
before_destroy :change_employer_users_counter_cache_after_destroy
before_create :change_employer_users_counter_cache_after_create
before_update :change_employer_users_counter_cache_after_update
def change_employer_users_counter_cache_after_create
Operator.increment_counter(:users_count, self.operator_id) if self.current == true
end
def change_employer_users_counter_cache_after_update
if self.current_changed?
if self.current == true
Operator.increment_counter(:users_count, self.operator_id)
else
Operator.decrement_counter(:users_count, self.operator_id)
end
end
end
def change_employer_users_counter_cache_after_destroy
Operator.decrement_counter(:users_count, self.operator_id)
end
end
the gem "counter_culture" handled this very nicely...and cleaned up my code.
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.