Rails - Iterating a Nested Attributes of has_many association before saving - ruby-on-rails

I have a model and a has_many association.
Say,
class Invoice < ActiveRecord::Base
has_many :lines, class_name: 'InvoiceLine', inverse_of: :invoice, dependent: :destroy
accepts_nested_attributes_for :lines, reject_if: :invalid_line, allow_destroy: true
end
Is it possible for me to iterate through the lines before it is saved (especially during an update), either in InvoicesController or in Invoice model?
When I iterate on the existing record during an update, I get old lines, not the updated lines from the view.
Currently I am doing the following in the controller. I am not quite happy with this.
total = params["invoice"]["lines_attributes"].
map{|k,v| [v["amount"], v["_destroy"]]}.
select{|x| x[1] == "false"}.
map{|x| x[0].to_f}.
inject {|total, amount| total + amount}

You can use assign_attributes and marked_for_destruction? as:
#invoice.assign_attributes(params[:invoice])
total = #invoice.lines.select(&:marked_for_destruction?).map(&:amount).sum
#invoice.save #save all on db

Related

Feed from multiple models (sorted by the time they were added to the feed)

I'm working on a simple Rails bookmarking app that lets users save Items and organize them in Collections.
I'd like to add an index page with staff picks that includes both selected Items and Collections. The result would be a feed that ranks these Items and Collections by the time they were selected as staff picks (not by the time they were initially created).
Here's what I have so far:
Feed Model
class Feed < ApplicationRecord
has_many :feed_entries, dependent: :destroy
has_many :items, through: :feed_entries
has_many :collections, through: :feed_entries
end
Feed Entry Model
class FeedEntry < ApplicationRecord
belongs_to :item
belongs_to :collection
belongs_to :feed
end
Item Model
class Item < ApplicationRecord
has_many :feed_entries, dependent: :destroy
has_many :feeds, through: :feed_entries
accepts_nested_attributes_for :feeds
end
Collection Model
class Collection < ApplicationRecord
has_many :feed_entries, dependent: :destroy
has_many :feeds, through: :feed_entries
accepts_nested_attributes_for :feeds
end
Feeds Controller
class FeedsController < ApplicationController
def index
#feed = Feed.find_by(feed_name: 'staffpicks')
#feed_items = #feed.items.includes(:feed_entries).order('feed_entries.created_at DESC')
#feed_collections = #feed.collections.includes(:feed_entries).order('feed_entries.created_at DESC')
end
end
So at this point I'm able to list staff-picked Items and Collections sorted by the time they were selected. But I can't figure out how to combine the two.
#everything = (#feed_collections + #feed_items)
I tried this, but I can't use order on an array and when I append .sort_by(&:created_at) it sorts by the creation date of the respective Item and Collection - not by when they were added to the array.
Any ideas how I can solve this?
Many thanks in advance. I'm new to programming and Rails, so your feedback is very much appreciated :)
class Feed < ApplicationRecord
has_many :feed_entries, dependent: :destroy,
-> { order(created_at: :desc) }
has_many :items, through: :feed_entries
has_many :collections, through: :feed_entries
end
class FeedEntry < ApplicationRecord
belongs_to :item, optional: true
belongs_to :collection, optional: true
belongs_to :feed
def entry
item || collection
end
end
#feed = Feed.eager_load(feed_entries: [:item, :collection]).find(1)
#entrees = #feed.feed_entries.map(&:entry)
You should also consider setting this up as a polymorphic association instead of using two different foreign keys as that will let you treat both items and collections as single homogenous association.
Just use the feed_entries association.
#feeds = feed_entries.includes(:item, :collection).order(“feed_entries.created_at desc”)

Ruby on Rails optimal search with array of filter values?

I have a specific example, but the general question is: whats the optimal way to retrieve records when you have an array of filter values to match?
Say I have User and Post records where User has_many :posts
and I have a Relationship model that looks like this:
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
I want to write a function that returns chronologically ordered posts from your followings' following - that is, all the posts from users that the users you follow are following, excluding duplicates and posts from anyone you follow.
My solution is to first compile an array of all the users of interest:
users = []
#user.following.each do |f|
f.following.each do |ff|
users << ff
end
end
# dedupe
users = users.uniq
#remove self and following
users.delete(#user)
#user.following.each do |f|
users.delete(f)
end
then compile their posts and order them:
posts = []
users.each do |u|
posts += u.posts
end
posts.sort_by!{|x| x[:created_at]}.reverse!
I think there's a better way to do this with Active Record functions, but I can't figure out how to make them work with an array. For instance, if I compile an array of User id values instead of the full models, and try to run this code to get the post array:
posts = Post.where(
user_id: user_ids
).order('created_at DESC').limit(21)
it returns an empty array. Is there a better way to search with an array of filter values than my current solution?
Update: additional modal code:
class Post < ActiveRecord::Base
belongs_to :user
...
User.rb
class User < ActiveRecord::Base
has_many :photos
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
...
Your idea of using user_ids is a good one. If that query returned an empty array, then did you check to make sure that the user_ids were the ones that you expected?
As for the code, you need to look into Enumerable#map and #flat_map. They are built-in ruby methods to accomplish what you're trying to do with the #each loops. Your code might reduce to something like:
user_ids = user.followings.flat_map { |following| following.following_id }
user_ids.uniq!
user_ids -= [user.id, user.following_ids].flatten
Post.where(user_id: user_ids).order(id: :desc).limit(21)
Note: Since created_at should follow id creation order, I would consider searching based on id rather than created_at since it should have an index.

Avoid duplicate entry with has_many through rails

I have been using this nested attributes with has_many :through
class Check < ActiveRecord::Base
has_many :checks_tags, dependent: :destroy
has_many :tags, through: :checks_tags
attr_accessible :tags_attributes, :checks_tags_attributes
accepts_nested_attributes_for :tags, :checks_tags
end
class Tag < ActiveRecord::Base
has_many :checks_tags, dependent: :destroy
has_many :checks, through: :checks_tags
end
class CheckTag < ActiveRecord::Base
belongs_to :check
belongs_to :tag
end
so here the issue is when i create with this hash
"tags_attributes"=>[{"id"=>"", "name"=>"test12", "company_id"=>"1"}, {"id"=>"", "name"=>"test12", "company_id"=>"1"}]
actually here have two tags with same name, so its creating Tag twice and after putting twice on CheckTag, so is there any way to avoid this creation as twice in Tag?
If you want it forbidden in the database, you could create a unique index on the combination of the two columns on the check_tag table. If you want to handle it in rails, you can do it with a before_save callback on the Check model (if that's the only way you create these), but that may make you vulnerable to a race condition.
See this answer:
Index on multiple columns in RoR

Why aren't my nested attributes being destroyed along with the parent?

I have a Resume Model that has_many :skills, and my skills model that belongs_to :resume.
I am nesting the skills form inside the resume form and it's creating the record and relationship perfectly. But when I try to destroy the resume, the associated Skill is not being destroyed along with it.
Here are my models:
# Resume.rb
class Resume < ActiveRecord::Base
has_many :skills
belongs_to :user
accepts_nested_attributes_for :skills, allow_destroy: true
end
# Skill.rb
class Skill < ActiveRecord::Base
belongs_to :resume
end
Here's the strong params in the resume_controller.rb
def resume_params
params.require(:resume).permit(:user_id, :title, :summary, :job_title, skills_attributes [:skill_name, :_destroy])
end
As far as I can tell I am passing the _destroy key properly. I noticed some people had _destroy checkboxes in the form. I want the Skills to be deleted when I Destroy the entire resume. Thanks!
All you have specified is that you can destroy skills on a resume like with the checkboxes you mention seeing in some examples. If you want it to destroy all skills associated when a resume is destroyed you have adjust your has_many declaration.
has_many :skills, dependent: :destroy
Add :dependent => :destroy in your Resume model like below:
class Resume < ActiveRecord::Base
has_many :skills, :dependent => :destroy
belongs_to :user
accepts_nested_attributes_for :skills, allow_destroy: true
end
:dependent Controls what happens to the associated objects when their owner is destroyed:
:destroy causes all the associated objects to also be destroyed
:delete_all causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
:nullify causes the foreign keys to be set to NULL. Callbacks are not executed.
:restrict_with_exception causes an exception to be raised if there are any associated records
:restrict_with_error causes an error to be added to the owner if there are any associated objects

Validate presence of foreign key on associated objects and using collection.build

I'm having the following models:
class Price < ActiveRecord::Base
belongs_to :article, inverse_of: :prices
validates :article_id, presence: true
end
class Article < ActiveRecord::Base
has_many :prices, dependent: :destroy, inverse_of: :article
end
The code when creating them raises validation error when saving (Prices is invalid):
article = Article.new
article.prices.build( { amount: 55.0 } )
article.save! #=> Validation failed: Prices is invalid
So Rails isn't smart enough to save the parent object (Article) before the child objects (Prices) so article_id can be assigned to the price before it is saved.
How do you use validations on the foreign key when using the build function?
It seems like a pretty standard scenario that should work?
(I know you can use database level constraints, but I'm asking about application level validations here)
In Rails way you can do like this
class Article < ActiveRecord::Base
has_many :prices, dependent: :destroy, inverse_of: :article
validates_associated :prices
end
but this is not 100% solution to this.
You can try this gem https://github.com/perfectline/validates_existence

Resources