Is the Active Record Base update method deprecated? - ruby-on-rails

I'm trying to update many active records at the same time using the :update method and they don't seem to update fine.
#drop_ship_order_line_items = DropShipOrderLineItem.update(params[:drop_ship_order_line_items].keys, params[:drop_ship_order_line_items].values).reject { |dsoli| dsoli.errors.empty? }
params[:drop_ship_order_line_items] returns the following hash:
{"11"=>{"available"=>"1"}, "2"=>{"available"=>"1"}}
But the models don't seem to update correctly...anyone with insides?

AFAIK you can't update models like this on rails, you would have to do it like this:
params[:drop_ship_order_line_items].each do |key,value|
DropShipOrderLineItem.find( key ).update_attributes( value )
end
EDIT
There's probably an attr_protected call somewhere in your code, you should check which attributes are protected or not in there.
If you think you can safely ignore the protection on this specific call, you can use some sending do work out the magic (disclaimer: this is on your own, i'm just showing a possibility):
params[:drop_ship_order_line_items].each do |key,value|
ship = DropShipOrderLineItem.find( key )
value.each do |property,value|
ship.send( "#{property}=", value )
end
ship.save
end
This is going to overcome the attribute protection, but you should make sure this is a safe call and you're not going to burn yourself by doing this.

Related

Should we use strong params when we update only one attribute?

I'm working on a Rails app and I have several actions( #delete_later, #ban_later and so on) where I only set one attribute from the request parameter( specifically, a reason field for doing that action).
I was wondering if it is ok to do it like this:
def ban_later
#object.reason = params[:object][:reason]
#object.save
end
Or is it a best practice to use strong params even in this situation?
def ban_later
#object.reason = object_params[:reason]
#object.save
end
private
def object_params
params.require(:object).permit(:permitted_1, :permitted_2, :reason)
end
Which of these solutions is the best one? If none of them is, then what's the best solution to my problem?
Later Edit:
The #ban_later, #delete_later actions can indeed set a flag column status but that can be done without receiving it's value from the params hash. Since you will only set one status per method you can simply set the status "pending_delete" when you are in #delete_later and "pending_ban" when you are in #ban_later.
Later Later Edit
Why use #save and not update_attributes directly? Let's say you need to have a if #object.save statement. On the false branch( object not saved) you might still want to render a view where the contents of that #object are used.
First one saves computation.
Second one checks for existence of :object sub-hash, which I think is good for fault-tolerance.
I initially would pick the 1st, but after some thought I liked the second one more.
The simplest answer is that if you only use one parameter in params, and do not pass it to a multi attribute setter like model#create then you don't have to use strong_parameters to get a secure solution.
However, I expect that it is unlikely that this is the case for the whole controller. Where the ban_later method only needs one parameter, other controller methods will need more. In this case the question becomes: "do you want to handle params differently for ban_later to how you use it for the other controller methods?".
Also can you be sure that the functionality will not change, and that when you change the functionality, that you'll remember to change the way params is handled.
Therefore, I would use strong_parameters because it means:
parameters are handled consistently across all methods in the controller.
changes to methods are less likely to expose vulnerabilities as functionality changes.
If you're updating a single attribute, why don't you use the update_attributes method? (update_attribute doesn't invoke validation)
def ban_later
#object.update_attributes reason: params(:reason)
end
private
def params params
params = %i(:permitted_1, :permitted_2, :permitted_3) unless params
params.require(:object).permit params
end
In light of the comments by ReggieB, you could also use the update option:
def ban_later
#object.update reason: params(:reason)
end
As mentioned, Reggie and the other answers explain the schematics of how this works best (IE with mass-assignment etc). Above is actionable code which you're free to use.
The bottom line here is that if you want to keep your application versatile (IE having ultimate extensibility wherever you need), you'll need to adhere to the strong params setup.
The other answers outline how that setup works, and how its functionality is different dependent on what you need.
I have included a trick to make it so you only accept specific params in your params method. I've not tested it extensively, so we may have to refactor it to get the required result.
After strong parameters check why not just update the object? Its just a standart workflow. (Please tell me if there are any reasons not to do that in your situation)
def ban_later
#object.update(object_params)
# dont forget validation check
end
private
def object_params
params.require(:object).permit(:permitted_1, :permitted_2, :reason)
end
In this case it'd be much easier to add more updateble fields.

Understanding "attribute_will_change!" method

I want to overwrite the store_accessor's getter. Which can be found here. Code here:
# File activerecord/lib/active_record/store.rb, line 74
def store_accessor(store_attribute, *keys)
keys = keys.flatten
_store_accessors_module.module_eval do
keys.each do |key|
define_method("#{key}=") do |value|
write_store_attribute(store_attribute, key, value)
end
define_method(key) do
read_store_attribute(store_attribute, key)
end
end
end
self.stored_attributes[store_attribute] ||= []
self.stored_attributes[store_attribute] |= keys
end
I achieved the functionality I was after, however if I were to also overwrite the setter, there is a method that is not clear to me, which is within the write_store_attribute(...) method (found here).
The code is here:
# File activerecord/lib/active_record/store.rb, line 108
def write_store_attribute(store_attribute, key, value)
attribute = initialize_store_attribute(store_attribute)
if value != attribute[key]
send :"#{store_attribute}_will_change!"
attribute[key] = value
end
end
The method I don't understand is "#{whatever_store_att}_will_change!".
If I were to overwrite the setter I would use update_attributes or update_column. Is this method making the assignment attribute[key]=value actually modify the field in the DB, making it equivalent to a update_attributes?
This is part of activemodel's change tracking system, which active record uses to know what it needs to write to the db (if anything).
When you call save on a record, then activerecord creates an update query for all the attributes that it considers changed (and if there are no changed attributes then it does nothing at all).
The accessors that activerecord generates for you handle this for you automatically, but in some cases its useful to tell activerecord that an attribute is dirty and needs changing (for example if you update something in place, circumventing the usual change tracking mechanism).
This is exactly what happens here: when you set a key for the stored attribute - rails modifies the hash (as opposed to assigning a new hash), and so a call to _will_change! is needed to keep the change tracking informed that the underlying attribute will need writing to the db the next time save is called.

Rails 4: Create object only in a factory method?

Going to simplify a bit here, but assume an app that has Users and UserRecords. A User must have one or more UserRecords. I want to limit the creation of UserRecords to a method in User, namely #create_new_user_record.
In other words, I don't want to allow UserRecord.new or UserRecords.create anywhere else in the application. I need to control the creation of these records, and perform some logic around them (for example, setting the new one current and any others to not current), and I don't want any orphaned UserRecords in the database.
I tried the after_initialize callback and checking if the object is new and raising an error there, but of course I do need to call UserRecord.new in User#create_new_user_record. If I could somehow flag in #create_new_user_record that I am calling new from that method, and pick that up in after_intialize, that would work, but how?
I might be over thinking it. I can certainly create a that method on User, and just 'know' to always call it. But others will eventually work on this app, and I will go away and come back to it as some point.
I suppose I could raise the error and just rescue from it in #create_new_user_record. Then at least, if another develop tries it elsewhere they will find out why I did it when they pursue the error.
Anyway, wondering what the Rails gurus here had to say about it.
super method is what you are looking for. Though you'll need some workaround (maybe simple check for value of option only you know about) to fit your needs
class User < ActiveRecord:Base
def .new(attributes = nil, options = {})
do_your_fancy_stuff
if option[:my_secret_new_method]
super # call AR's .new method and automatically pass all the arguments
end
end
Ok, here's what I did. Feel free to tell me if this is bad idea or, if it's an ok idea, if there's a better way. For what it's worth, this does accomplish my goal.
In the factory method in the User model, I send a custom parameter in the optional options hash defined on the new method in the API. Then I in the UserRecord#new override, I check for this parameter. If it's true, I create and return the object, otherwise I raise in custom error.
In my way of thinking, creating a UserRecord object any other way is an error. And a developer who innocently attempts it would be lead to explanatory comments in the two methods.
One thing that's not clear to me is why I need to leave off the options hash when I call super. Calling super with it causes the ArgumentError I posted in my earlier comment. Calling super without it seems to work fine.
class User < ActiveRecord::Base
...
def create_new_user_record
# do fancy stuff here
user_record = UserRecord.new( { owner_id: self.id, is_current: true }, called_from_factory: true )
user_record.save
end
...
end
class UserRecord < ActiveRecord::Base
...
def UserRecord.new(attributes = nil, options = {})
if options[:called_from_factory] == true
super(attributes)
else
raise UseFactoryError, "You must use factory method (User#create_new_user_record) to create UserRecords"
end
end
...
end

Rename ActiveResource properties

I am consuming JSON data from a third party API, doing a little bit of processing on that data and then sending the models to the client as JSON. The keys for the incoming data are not named very well. Some of them are acronyms, some just seem to be random characters. For example:
{
aikd: "some value"
lrdf: 1 // I guess this is the ID
}
I am creating a rails ActiveResource model to wrap this resource, but would not like to access these properties through model.lrdf as its not obvious what lrdf really is! Instead, I would like some way to alias these properties to another property that is named better. Something so that I can say model.id = 1 and have that automatically set lrdf to 1 or puts model.id and have that automatically return 1. Also, when I call model.to_json to send the model to the client, I dont want my javascript to have to understand these odd naming conventions.
I tried
alias id lrdf
but that gave me an error saying method lrdf did not exist.
The other option is to just wrap the properties:
def id
lrdf
end
This works, but when I call model.to_json, I see lrdf as the keys again.
Has anyone done anything like this before? What do you recommend?
Have you tried with some before_save magic? Maybe you could define attr_accessible :ldrf, and then, in your before_save filter, assign ldrf to your id field. Haven't tried it, but I think it should works.
attr_accessible :ldrf
before_save :map_attributes
protected
def map_attributes
{:ldrf=>:id}.each do |key, value|
self.send("#{value}=", self.send(key))
end
end
Let me know!
You could try creating a formatter module based on ActiveResource::Formats::JsonFormat and override decode(). If you had to update the data, you'd have to override encode() also. Look at your local gems/activeresource-N.N.N/lib/active_resource/formats/json_format.rb to see what the original json formatter does.
If your model's name is Model and your formatter is CleanupFormatter, just do Model.format = CleanupFormatter.
module CleanupFormatter
include ::ActiveResource::Formats::JsonFormat
extend self
# Set a constant for the mapping.
# I'm pretty sure these should be strings. If not, try symbols.
MAP = [['lrdf', 'id']]
def decode(json)
orig_hash = super
new_hash = {}
MAP.each {|old_name, new_name| new_hash[new_name] = orig_hash.delete(old_name) }
# Comment the next line if you don't want to carry over fields missing from MAP
new_hash.merge!(orig_hash)
new_hash
end
end
This doesn't involve aliasing as you asked, but I think it helps to isolate the gibberish names from your model, which would never have to know those original names existed. And "to_json" will display the readable names.

Use find to initialize a constant?

Something like this:
class Category
SOME_CATEGORY = find_by_name("some category")
end
Category::SOME_CATEGORY
tried without a problem, but want to know if it is a bad idea, and the reasons if any..
thanks
If you don't want to hit the database each time you'll have to cache the model. There are several ways to do this, but one quick way is using Memoization. This was introduced in Rails 2.2.
class Category < ActiveRecord::Base
class << self
extend ActiveSupport::Memoizable
def named(name)
find_by_name(name)
end
memoize :named
end
end
Use it like this.
Category.named("some category") # hits the database
Category.named("some category") # doesn't hit the database
The cache should stay persistent across requests. You can reset the cache by passing true as the last parameter.
Category.named("some category", true) # force hitting the database
What do you want to do?
Maybe:
class Category
def self.some_category
Category.find_by_name("some category")
end
end
So you can call:
Category.some_category
=> <Category#2....>
It's not a terrible idea, but it's not really a good one either. It doesn't really fall in line with the way Rails does things. For one thing, you'll end up with a lot of ugly constant code. Too many ALL_CAPS_WORDS and your Ruby starts to look like C++. Bleah.
For another, it's inflexible. Are you going to make one of these constants for every category? If you add a new category two months from now, will you remember to update your Rails code, add a new constant, redeploy it and restart your server?
If it's important to you to be able to access categories very easily, and not repeat DB queries, here's a bit of metaprogramming that'll automatically look them up and create static methods like Lichtamberg's for you on first access:
def self.method_missing(category, *args) # The 'self' makes this a class method
#categories ||= {}
if (#categories[category] = find_by_name(category.to_s))
class_eval "def self.#{category.to_s}; #categories[#{category}]; end"
return #categories[category]
end
super
end
With this method in place, whenever you first call Category.ham, it'll create a class method that returns the value of find_by_name("ham") -- so that neither the query nor method_missing() runs again the next time you call it. This is pretty much the way the OpenStruct class works, BTW; look it up in the Pickaxe book if you want to learn more.
(Of course you'll still have the risk that, because these are all memoized, your Rails app won't reflect any changes you make to your category objects. This makes the assumption that changes won't happen or don't really matter. It's up to you to determine whether that assumption is valid for your app. You could always put an after_update callback in your code that resets ##categories if that's a problem; but at that point this starts to get complicated.)

Resources