Sorting as association before saving using callbacks - ruby-on-rails

I recently migrated from Rails 3 to Rails 4 and in the process I noticed that sorting association does not work in Rails 4. Following are the sample models:
#box.rb
class Box < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items, :allow_destroy => true
before_validate
items.sort! { <some condition> }
end
end
#item.rb
class Item < ActiveRecord::Base
belongs_to :box
end
In Rails 3, sort! method on the association modified the items hash, but in Rails 4 it returns a new sorted instance but does not modify the actual instance. Is there a way to overcome this?

Try this:
#box.rb
class Box < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items, :allow_destroy => true
before_validate
self.items = items.sort { <some condition> }
end
end
#item.rb
class Item < ActiveRecord::Base
belongs_to :box
end

Sorting before storing won't really help. When you pull them from the database they might not be in that order. You wan't to specify the order on the has many (or in the item model).
If you have a more complicated ordering, then please post that logic.
class Box < ActiveRecord::Base
# Can replace position with hash like: { name: :asc }
has_many :item, -> { order(:position) }
accepts_nested_attributes_for :items, :allow_destroy => true
before_validate
items.sort! { <some condition> }
end
end

Related

Rails 4 - Multiple Nested Form

I trying to create a nested form with N models associated.
This is the schema:
and I need a Edit-Form which iterate through all objects till the last one:
My delivery object looks like this:
class Delivery < ActiveRecord::Base
has_many :boxes
and this is the box object
class Box < ActiveRecord::Base
belongs_to :box, :polymorphic => true, :inverse_of => :box
has_many :boxes
accepts_nested_attributes_for :boxes
In the deliveries controller :
def new
#delivery = Delivery.new
# if you want 3 instantiated objects
3.times { #delivery.boxes.build }
end

How to add conditions for json model association

Im trying to include an association but only ones based on its attribute's value
Joo Model:
def as_json(options = {})
super(include: [:foo, (:bar).where('bar.accepted = ?', true)])
end
undefined method `where' for :bar:Symbol
Doing super(include: [:foo, :bar]), I have no control of what I want. How to accomplish what Im trying to do? Only include where bar.accepted == true
I was looking at this to see if it was possible. Im using Rails 5 API.
Edited to show associations:
Joo:
has_many :bars, dependent: :destroy
belongs_to :foo
Foo:
has_many :bars, dependent: :destroy
Bar:
belongs_to :foo
belongs_to :joo
As per the doc I see there isn't way to include associations conditionally. I think you can make a conditional association on model Joo and call it in as_json
Class Joo < ApplicationRecord
belongs_to :foo
has_many :bars, dependent: :destroy
has_many :accepted_bars, -> { where accepted: true }, class_name: "Bar"
end
Then you call it like
#joo = Joo.all.reverse
#joo.as_json(include: [:foo, :accepted_bars])
Note: Not tested!

Maintain order of saved attributes list for model

I have two models with a has_many to has_many relationship through a join table.
class Article < ActiveRecord::Base
has_many :authorings, -> { order(:position) }, :dependent => :destroy
has_many :authors, through: :authorings
end
class Author < ActiveRecord::Base
has_many :authorings, -> { order(:position) }
has_many :articles, through: :authorings
end
class Authoring < ActiveRecord::Base
belongs_to :author
belongs_to :article
acts_as_list :scope => :author
end
The getter and setter methods for the array
def author_list
self.authors.collect do |author|
author.name
end.join(', ')
end
def author_list=(author_array)
author_names = author_array.collect { |i|
i.strip.split.each do |j|
j.capitalize
end.join(' ') }.uniq
new_or_found_authors = author_names.collect { |name| Author.find_or_create_by(name: name) }
self.authors = new_or_found_authors
end
I want to maintain the order of the list of authors that get saved to the model. That is, I would like to be able to change and reorder the author_list and have it retrieved in that order for the view. I want to be to change it ['foo','bar'] or ['bar','foo']. How can I do this?
As a note, I have tried using acts_as_list and added a position column to the database for authoring but to no success.
You need to have a position attribute for each author.
Then you can generate a sorted activerecord array and grab the name attribute
authorlist = Author.order(:position).pluck(:name)
I'm not seeing how you're changing the position attribute, I'm guessing you need some sort of js on the frontend to do that.

how to give query in has_many through association Rails

I have following association
Mobile.rb
has_many :mobile_networks, :dependent => :destroy
has_many :networks, :through => :mobile_networks
Network.rb
has_many :mobiles, :through => :mobile_networks
MobileNetwork.rb
belongs_to :mobile
belongs_to :network
I want to query all mobile_networks of mobile and after that I want to check all network whether it is active or not something like I have written this code in my helper where I am getting mobile
def mobile_state(mobile)
mobile.mobile_networks.each do |e|
e.network.is_checked #true
end
end
So i need to do this in a query. Please guide me how to do this.
You could do the following:
Mobile.joins(:networks).where(id: params[:id]).each do |m|
m.networks.map(&:is_checked?)
end
MobileNetwork.mobiles(params[:id]).joins(:network).merge(Network.checked)
class Network < ActiveRecord::Base
scope :checked -> {
where(is_checked: true)
}
end
class MobileNetwork < ActiveRecord::Base
scope :mobiles, ->(*m) {
where(mobile_id: m.flatten.compact.uniq)
}
end

Rails order by in associated model

I have two models in a has_many relationship such that Log has_many Items. Rails then nicely sets up things like: some_log.items which returns all of the associated items to some_log. If I wanted to order these items based on a different field in the Items model is there a way to do this through a similar construct, or does one have to break down into something like:
Item.find_by_log_id(:all,some_log.id => "some_col DESC")
There are multiple ways to do this:
If you want all calls to that association to be ordered that way, you can specify the ordering when you create the association, as follows:
class Log < ActiveRecord::Base
has_many :items, :order => "some_col DESC"
end
You could also do this with a named_scope, which would allow that ordering to be easily specified any time Item is accessed:
class Item < ActiveRecord::Base
named_scope :ordered, :order => "some_col DESC"
end
class Log < ActiveRecord::Base
has_many :items
end
log.items # uses the default ordering
log.items.ordered # uses the "some_col DESC" ordering
If you always want the items to be ordered in the same way by default, you can use the (new in Rails 2.3) default_scope method, as follows:
class Item < ActiveRecord::Base
default_scope :order => "some_col DESC"
end
rails 4.2.20 syntax requires calling with a block:
class Item < ActiveRecord::Base
default_scope { order('some_col DESC') }
end
This can also be written with an alternate syntax:
default_scope { order(some_col: :desc) }
Either of these should work:
Item.all(:conditions => {:log_id => some_log.id}, :order => "some_col DESC")
some_log.items.all(:order => "some_col DESC")
set default_scope in your model class
class Item < ActiveRecord::Base
default_scope :order => "some_col DESC"
end
This will work
order by direct relationship has_many :model
is answered here by Aaron
order by joined relationship has_many :modelable, through: :model
class Tournament
has_many :games # this is a join table
has_many :teams, through: :games
# order by :name, assuming team has this column
def teams
super.order(:name)
end
end
Tournament.first.teams # are returned ordered by name
For anyone coming across this question using more recent versions of Rails, the second argument to has_many has been an optional scope since Rails 4.0.2. Examples from the docs (see scopes and options examples) include:
has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
has_many :comments, -> { order("posted_on") }
has_many :comments, -> { includes(:author) }
has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
has_many :tracks, -> { order("position") }, dependent: :destroy
As previously answered, you can also pass a block to has_many. "This is useful for adding new finders, creators and other factory-type methods to be used as part of the association." (same reference - see Extensions).
The example given there is:
has_many :employees do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
In more modern Rails versions the OP's example could be written:
class Log < ApplicationRecord
has_many :items, -> { order(some_col: :desc) }
end
Keep in mind this has all the downsides of default scopes so you may prefer to add this as a separate method:
class Log < ApplicationRecord
has_many :items
def reverse_chronological_items
self.items.order(date: :desc)
end
end

Resources