How do I prevent an ActiveResource model from making a request? - ruby-on-rails-5

I have some ActiveResource models like so:
class Meeting
has_many :sections
end
class Section
has_many :items
end
class Items
has_many :items
end
When I do a find on a Meeting, I get a json response like:
{
id: 1, ...
sections: [
{
id: 1, ...
items: [
{id: 16, ...
items: [
{id: 534, ...
items: [
{id: 8976, ...},
{id: 8977, ...}
]}
]}
]
}
}
And run the following (heavily edited) routine:
#meeting = Meeting.find(#meeting_guid)
import_sections
private
def import_sections
#meeting.sections.each do |section|
new_section = Item.find_or_initialize_by(foreign_guid: section.guid)
new_section.update!(layout: :section)
import_items(section, new_section)
end
end
def import_items(item, parent)
item.items.each do |item|
position = mema_item.orderpreference.to_i + 1
sub_item = Item.find_or_initialize_by(foreign_guid: item.guid)
sub_item.update!(layout: :item, title: item.name, parent: parent)
import_items(item, sub_item)
end
end
When it hits Items 8976 and 8977, it makes requests to /items?item_id=8976 and /items?item_id=8977. Is there a way to prevent it from making these requests when it hits the end of the last child? I don't think I'll ever call Items directly, so can I disable requests on the Item model or something?

I feel silly for not catching this, but I was able to solve the problem quite simply, albeit not through an ActiveResource feature. On the recursive import_items call, I check for if item.attributes.key? 'items', essentially just checking the attributes on the parent object without going into the relationship and creating an api call.

Related

nested_attributes and saved_changes does not work as expected

I'm having a problem with saved_changes ActiveRecord method returning blank values when the change was triggered by a child updated from nested attributes.
Given these two models:
class Customer < ApplicationRecord
enum status: {inactive: 0, active: 1, paused: 2}, _prefix: :status
has_many :customer_pauses
has_many :contracts
after_save :handle_status_change
protected
# UNEXPECTED BEHAVIOR HAPPENS HERE!!!
def handle_status_change
puts "CHANGED STATUS??? #{saved_change_to_status?}"
puts saved_changes
puts "--------------------"
if saved_change_to_status?
contracts.where(status: saved_change_to_status.first).update status: status
end
end
end
class CustomerPause < ApplicationRecord
enum status: {disabled: 0, scheduled: 1, ongoing: 2, finished: 3}, _prefix: :status
belongs_to :customer
after_save :update_customer_status
protected
#update customer status to match the status of the pause accordingly
def update_customer_status
if (saved_change_to_status? || new_record?)
if status_ongoing? && customer.status_active?
customer.update status: Customer::STATUS_PAUSED
end
end
end
end
These differences in behavior happen:
customer = Customer.create status: :active
pause = customer.customer_pauses.create status: :disabled
pause.status = :ongoing
#this works as expected, and customer.saved_changes will contain the status change
pause.save
#This however will result in saved_changes to be empty on the customer callback "handle_status_change"
#Note that the customer status is still changed but not listed in saved_changes
customer.update customer_pauses_attributes: [pause.attributes]
I'm not sure why this happens, is this the expected behavior or a bug in rails?
Should a callback on a nested child not cause changes to its parent?

How to group Associations' attributes in a Collection (Active Model Serializer)

Consider the below two entities: Posts, Authors
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, author_id
belongs_to :author
end
Class AuthorSerializer < ActiveModel::Serializer
attributes :id, :name
end
Using the 'JSON' adapter, for the index action of PostsController we get the response as below:
{
"posts":[
{
"id":1,
"title":"Hic consectetur et delectus",
"author_id": 1,
"author":{"id":1,"name":"Author-A"}
},
{
"id":2,
"title":"Hic consectetur et delectus",
"author_id": 2,
"author":{"id":2,"name":"Author-B"}
},
{
"id":3,
"title":"Hic consectetur et delectus",
"author_id": 1,
"author":{"id":1,"name":"Author-A"}
}
]
}
Is it possible to group the Authors data outside the Posts data for sideloading? (as shown below)
{
posts:[{...}, {...}, {...}],
authors:[
{id:1, name:'Author-A'},
{id:2, name:'Author-B'}
]
}
Seems like you want to mimic the jsonapi included relationships. So, w json adapter, post has an attribute, to keep things simple, author_id, and then, you render the post collection separately from authors and generate the response, or you renders them mixed and reduce them. You may be able to just render as [posts, authors]!
As a workaround, we created a custom adapter extending the Json adapter and modifying the hash to our desired format in the serializable_hash(...) method
class CustomAdapter < ActiveModelSerializers::Adapter::Json
def serializable_hash(options = nil)
result_hash = super(options)
... code to modify hash format ...
result_hash
end
end

Rails - Serialize related data

I've got two models:
class Continent < ActiveRecord::Base
has_many :countries
end
class Country < ActiveRecord::Base
belongs_to :continent
end
I created controller like:
class ContinentsController < ApplicationController
def index
#continents = Continent.all
render json: #continents
end
end
and serializer:
class ContitnentSerializer < ActiveModel::Serializer
attributes :name, :countries
end
Here my issue begins. I'd like to serialize only countries with given condition where value comes from HTTP GET params. E.g country inside serializer should be displayed only if population is more than params[:population]. The problem is inside serializer we don't have access to params to examine that.
[
{
name: 'Europe'
countries: [
{
name: 'Italy',
population: 1000000
}
]
},
{
name: 'Africa'
countries: [
]
}
]
I've tried to join table with condition but it seems be not working.
#continents = Continent.all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > #{params[:population]}")
Create a scope and call the scope with param value from controller:
scope :population_more_than, ->(population) {all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > ?", population)}
Now call it from controller instead of Continent.all
Continent.population_more_than(params[:population])
You can try
#continents = Continent.all
#continents.num_population = params[:population]
render json: #continents.to_json(methods: :countries_with_population_gt)
in your Continent model
attr_accessor :num_population
def countries_with_population_gt(num_population=0)
countries.where('population > ?', #num_population)
end
Basically, you need to select only Continents that fall under specific rule. If this is a frequently used filter, then I would go with the Babar's suggestion and create a scope.
If this is a one time selection, then I prefer simply do filtering right there without cluttering up my models with non-frequently used scopes.
Continent.joins(:countries).where("countries.population > :population", population: params[:population])
# Or event shorter
Continent.joins(:countries).where("countries.population > :population", params)

How do I clear a Model's :has_many associations without writing to the database in ActiveRecord?

For the sake of this question, let's say I have a very simple model:
class DystopianFuture::Human < ActiveRecord::Base
has_many :hobbies
validates :hobbies, :presence => {message: 'Please pick at least 1 Hobby!!'}
end
The problem is that when a human is updating their hobbies on a form and they don't pick any hobbies, there's no way for me to reflect this in the code without actually deleting all the associations.
So, say the action looks like this:
def update
hobbies = params[:hobbies]
human = Human.find(params[:id])
#ideally here I'd like to go
human.hobbies.clear
#but this updates the db immediately
if hobbies && hobbies.any?
human.hobbies.build(hobbies)
end
if human.save
#great
else
#crap
end
end
Notice the human.hobbies.clear line. I'd like to call this to make sure I'm only saving the new hobbies. It means I can also check to see if the user hasn't checked any hobbies on the form.
But I can't do that as it clears the db. I don't want to write anything to the database unless I know the model is valid.
What am I doing wrong here?
Initialy I also did this same way. Then found out one solution for this issue.
You need to do something like this
params[:heman][:hobby_ids]=[] if params[:human][:hobby_ids].nil?
Then check
if human.update_attributes(params[:human])
Hope you will get some idea...
EDIT:
Make hobbies params like this
hobbies = { hobbies_attributes: [
{ title: 'h1' },
{ title: 'h2' },
{ title: 'h3', _destroy: '1' } # existing hobby
]}
if Human.update_atttributes(hobbies) # use this condition
For this you need to declare accepts_nested_attributes_for :hobbies, allow_destroy: true in your Human model.
See more about this here http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
You can try https://github.com/ryanb/nested_form for this purpose..

Netzke Grid filtering

I have an issue related to filtering data in Netzke Grid.
column :user_id do |c|
c.editor = {xtype: :combobox, editable: false, min_chars: 2}
end
It is mentioned in the doc that,
A hash that will override the automatic editor configuration. For example, for one-to-many association column you may set it to {min_chars: 1}, which will be passed to the combobox and make it query its remote data after entering 1 character (instead of default 4).
Seems {min_chars: 1} is not working as expected.
Please see example below for simple Customers grid and let me know if it works for you. Netzke way is to use __ (double underscore) to define one-to-many associations. This gives you combobox and all necessary data bindings. I tried different ways to make min_chars property work, but it all failed. Could be a bug. In the end, the only thing that worked is to do it from init_component method.
class Customers < Netzke::Basepack::Grid
def configure(c)
super
c.model = 'Customer'
c.columns = [
{ name: :name, header: 'Customer Name' },
{ id: :country__name, name: :country__name, header: 'Country' }
]
end
js_configure do |c|
c.init_component = <<-JS
function() {
this.callParent();
Ext.ComponentManager.get('country__name').editor.minChars = 2;
}
JS
end
end