Convenience of CollectionProxy for has_one? - ruby

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)

Related

how to update/create has_one associated data efficiently in rails

Hi i am learning rails and i got the situation where i have to update or create the data for has_one associated model/table.
I tried this
Worker.rb
class Worker < ApplicationRecord
has_one :worker_encrypted_info
end
worker_encrypted_info.rb
class WorkerEncryptedInfo < ApplicationRecord
belongs_to :worker
end
workers_controller.rb
def update_personal_info
if #worker.update(update_personal_info_params)
#worker.create_worker_encrypted_info(sin: params[:worker_encrypted_info][:sin])
end
end
now what
#worker.create_worker_encrypted_info(sin: params[:worker_encrypted_info][:sin])
is doing is first updating worker_encrypted_info record with worker_id: nil and then creating new record for same worker_id instead of updating the old one.
which is not very great ofcourse because i dont need that worker_id: nil record in my database at all.
why do i need to delete that previous one manually with .destroy code?
is there any Efficient way to do so?
I would expect that this works
def update_personal_info
return unless #worker.update(update_personal_info_params)
if #worker.worker_encrypted_info
#worker.worker_encrypted_info.update(sin: params[:worker_encrypted_info][:sin])
else
#worker.create_worker_encrypted_info(sin: params[:worker_encrypted_info][:sin])
end
end

Rails overriding active record setter in a relation

I want to override the << setter in my relation. For example, given:
class Library < ActiveRecord::Base
has_many :groups
def readers
groups.find_by(name: 'readers').users
end
end
class Group < ActiveRecord::Base
has_many :group_memberships
has_many :users, through: :group_memberships
end
class GroupMembership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class User < ActiveRecord::Base
has_many :groups, through :group_membership
end
I want to do something like
someLibrary.readers << user1
and some additional things to happen after this.
The code should look something like:
def <<(objects)
super objects
#do other things here
end
Where should it be? I guess in Group, like:
class Group
...
def users<<(objects)
super objects
#do stuff
end
end
but I only want to do it when I'm invoking << on readers.
I want to know if there is a way to know whether I'm invoking << on a group users relationship, or whether I have access to group object when I'm invoking << method on group users through the relationship.
I want to do it because it looks nice. The easiest way would be to define separate method to set readers (and be more explicit), but I want to know if it is possible in activerecord or in ruby.
edit:
Yeah I know that overriding core methods is bad thing and people go to hell for that, yada yada yada.
I'm just curious how it's done. Like, for learning purposes.
Besides the aim is just to override the << method on that particular relation so probable there might be some justification why someone might want to do it.
Obligatory disclaimer:
I do not recommend that you do this, in 'important' code. Changing the behaviour of methods like this will confuse the hell out of other developers (as well as your future self), and lead to all sorts of unintended behavioural changes!
But assuming that this is 'just for fun'...
Based on the information above, someLibrary.readers returns a collection of User records. So all we need to do is add the desired behaviour to that class.
Normally you can do this by just defining a class method, in one of two ways:
class User
def self.foo
puts 'this works!'
end
class << self
def bar
puts 'this works too!'
end
end
end
With the above in place, you can call the methods like:
someLibrary.readers.foo
someLibrary.readers.bar
...However, there is some rails black magic going on under the hood here. someLibrary.readers is actually an instance of User::ActiveRecord_Associations_CollectionProxy, and the above methods are being picked up dynamically and appended to ActiveRecord::Associations::CollectionProxy.
Because of this dynamic method definition, it is not possible to override existing Rails methods (such as <<) in this manner. Instead, we'll need to monkey-patch the User::ActiveRecord_Associations_CollectionProxy class directly:
class User
class ActiveRecord_Associations_CollectionProxy
def <<(objects)
super(objects)
# do stuff
end
end
end
If you're looking for a better way of doing this however, I'd recommend using a service object design pattern. You can then encapsulate any more complex/custom logic relating to creating/updating/deleting users, libraries, etc. in a clean and isolated abstraction.
The more established way to do this...
class Library < ActiveRecord::Base
has_many :groups
has_one :reader_group -> {groups.find_by(name: 'readers')}
has_many :readers, through: :reader_group, class_name: 'User', foreign_key: 'user_id'
end
And that's it. You can now do
my_library.readers << another_user

How can I create a Polymorphic Ruby Method?

I have a class -- AccountGroup -- which has a polymorphic relation to various Account classes (i.e. AwordsAccount, BingAccount, etc...). I've defined a helper method -- accounts -- that aggregates all of the different account types:
def accounts
adwords_accounts + bing_ads_accounts + facebook_accounts + linkedin_accounts
end
Now, I'd like to extend this method so that I can use it to add accounts as well as list them:
account_group.accounts << an_adwords_account
which should call:
account_group.adwords_accounts << an_adwords_account
under the hood. How do I differentiate between calling the method with the modifier << vs. calling it without the modifier?
Thanks!
Here's how I would implement this. The Account model uses single table inheritance and has a type column that distinguishes between the different account types:
class Account < ActiveRecord::Base
belongs_to :account_group
end
class AdwordsAccount < Account
end
class BingadsAccount < Account
end
class FacebookAccount < Account
end
class LinkedinAccount < Account
end
In your AccountGroup model you can then create associations to all of these without any problems:
class AccountGroup < ActiveRecord::Base
has_many :accounts
has_many :adwords_accounts
has_many :bingads_accounts
has_many :facebook_accounts
has_many :linkedin_accounts
end
Now everything works as expected and accounts contains all of the other types combined. You might need to call reload on the other associations when you add/remove accounts, but i'm not sure about that. Just try it out.

ActiveRecord Scope: Strange behaviour with select on relation

I have two different models with a 1:N relation.
Let's name them 'myobject' and 'related'
class Myobject < ActiveRecord::Base
has_many :related
scope :without_related, includes(:related).select{ |o| o.related.size == 0 }
end
class Related < ActiveRecord::Base
end
The defined scope seems to work great as long as I don't create new assignments from Myobjects to Related:
Direct rails c command "Myobject.includes(:related).select ... (as defined in Scope) works as expected
Calls to scope "Myobject.without_related" still return objects that have been assigned in the meantime
It seems that this can be fixed by restarting the rails console or restarting Webrick.
But I can't always restart a webapplication only because a relation between objects has been changed ;)
Is there any way to fix this problem or to write the scope in a better way?
PS: I need this query as scope to pass its name as group_method to a grouped_select in the form of the Myobject model
Your problem is that in fact your scope is not scope :)
Scopes must return relations, but your scope returns array.
Though it can work as you expect, if you wrap it in lambda
scope :without_related, lambda{ includes(:related).select{ |o| o.related.size == 0 } }
But I recommend to rewrite this code as usual class method to not mislead those who'll work with this code in future
def self.without_related
includes(:related).select{ |o| o.related.size == 0 }
end
or use counter cache, as advised in other answer.
I would recommend you to use counter_cache for this, you need to add column *related_count* of type int to Myobject, make migration and then you will be able to do so:
class Myobject < ActiveRecord::Base
has_many :related
scope :without_related, where(related_count: 0)
end
class Related < ActiveRecord::Base
belongs_to :myobject, counter_cache: true
end
After that you will have super fast scope for getting all objects with no related records and an a count of that objects as well
Or if you know column name that should be present in related table, use this definition:
class Myobject < ActiveRecord::Base
has_many :related
scope :without_related, includes(:related).where('related.id', true)
end

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.

Resources