I have several models with about 40 attributes each. They are normalized, it's just that the models we are dealing with have to have multiple attributes. Other than that it's a pretty standard rails 4.2 app ( upgrading from rails 3.2) . The app is used both to serve dynamic pages sprinkled with ajax calls, and json so it can be used by various json clients.
So a call to : http://example.com/products/1.json - returns json and http://example.com/products/1 returns haml-parsed view.
JavaScript library I'm using ( KendoUI) returns a whole record on update, not just fields that got updated. Currently there is no way to avoid unless I want to rewrite KendoUi Grid according to their support forum.
So user can search all the products, and I display all the attributes of the product to her, and then based on her access level she can update certain fields (several pricing points and description), however ajax request contains ALL the attributes. So as a result I get ActiveModel::ForbiddenAttributesError
How can I properly ( the rails way so to speak) filter out params that got updated? Right now I'm doing a hash comparison of @product.attributes && params[:product] to figure out if there are updated/ new attributes passed. .diff was deprecated as of Rails 4.0.2
My admins are allowed to change almost all attributes on these big models ( except timestamps, id, and a few others). What is the best way to do it rather than doing params[:product].require(:base_cost).permit(:vendor_cost, :client_cost) for 30 odd attributes? It quickly becomes a problem to maintain these lists if the app is in development and attributes change. I guess I could use some kind of CONSTANT - ALLOWED_ATTRIBUTES or ADMIN_ATTRIBUTES and USER_ATTRIBUTES, and pass that to permit. But kind of feels un-Railsy?
Part 1
Even if the function is deprecated you can still use this functionality. With what your trying to do a few might be useful here.
class Hash
def diff h2
self.dup.delete_if { |k, v| h2[k] == v }.merge(h2.dup.delete_if { |k, v| self.has_key?(k) })
end
def only *args
h = {}
args.each do |a|
h[a] = self[a]
end
h
end
def except *args
h = {}
(self.keys - args).each do |a|
h[a] = self[a]
end
h
end
end
Tests
2.0.0-p247 :001 > h1 = {a: 1, b: 2, c: 3} #=> {:a=>1, :b=>2, :c=>3}
2.0.0-p247 :002 > h2 = {a: 3, b: 2, c: 1} #=> {:a=>3, :b=>2, :c=>1}
2.0.0-p247 :003 > h1.except(:a) #=> {:b=>2, :c=>3}
2.0.0-p247 :004 > h1.except(:c) #=> {:a=>1, :b=>2}
2.0.0-p247 :005 > h1.except(:c, :a) #=> {:b=>2}
2.0.0-p247 :006 > h1.only(:c, :a) #=> {:c=>3, :a=>1}
One thing to keep in mind is the params may not truly come in as a hash, so you may have to call .to_hash
Part 2
class FooBar < ActiveRecord::Base
#this controls access restriction, assuming you keep this setup:
#delete this part and all fields are update-able (leave it undefined)
#no fields can be updated if UPDATABLE_FIELDS[type].nil? or UPDATABLE_FIELDS[type][0].nil?
UPDATABLE_FIELDS = {
admin: [:except, :id, :timestamp],
user: [:only, :name]
}
end
class ActiveRecord::Base
def fields_for_user_type type
return true unless defined?(UPDATABLE_FIELDS)
return nil if !UPDATABLE_FIELDS[type] || !UPDATABLE_FIELDS[type].first
return UPDATABLE_FIELDS[type].first, UPDATABLE_FIELDS[type][1..-1]
end
end
class ApplicationController < ActionController::Base
def filter_params data, cls
method, restriction = cls.fields_for_user_type(current_user.type)
return data if method === true
return {} if method.nil?
return data.except(restriction) if method == :except
return data.only(restriction) if method == :only
end
end
class FunController < ApplicationController
def update
record = FooBar.find(params[:id])
record.update(filter_params(params[:data], FooBar))
end
end
You could add defaults to this pretty easily and some other nifty pieces of functionality but at least as a start this should do it for you.
Please keep in mind not all this has been thoroughly tested, there could be bugs!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With