I'm serializing many attributes on a model Page as hashes.
Because of the high number of attributes, I've taken a meta-programming approach and want to use .send() to iterate through a collection of attributes (such that I don't have to type out an update action for each attribute.
I've done something like this:
insights.each do |ins|
self.send("#{ins.name}=", {(Time.now) => ins.values[1]['value'].to_f})
self.save
end
The problem is that this obviously overwrites the whole serialized column, whereas I wish to add this as an element to the serialized hash.
Tried something like this:
insights.each do |ins|
self.send("#{ins.name}[#{Time.now}]=", ins.values[1]['value'].to_f)
self.save
end
But get a NoMethodError: undefined method page_fan_adds_unique[Mon Aug 13 13:31:58 -0400 2012]=
In the console I'm able to do Page.find(5).page_fan_adds_unique[Time.now]= 12345 and save it as an additional element to the hash as expected.
So how can I use .send() to save an additional element to a serialized hash? Or is there some other approach? Such as using update_attribute or another method? Writing my own? Any help is appreciated, even if the advice is that I shouldn't be using serialization for this.
I'd do :
self.ins.name.send(:[]=, key, value)
Related
I have a user model with a friends column of type text. This migration was ran to use the array feature with postgres:
add_column :users, :friends, :text, array: true
The user model has this method:
def add_friend(target)
#target would be a value like "1234"
self.friends = [] if self.friends == nil
update_attributes friends: self.friends.push(target)
end
The following spec passes until I add user.reload after calling #add_friend:
it "adds a friend to the list of friends" do
user = create(:user, friends: ["123","456"])
stranger = create(:user, uid: "789")
user.add_friend(stranger.uid)
user.reload #turns the spec red
user.friends.should include("789")
user.friends.should include("123")
end
This happens in development as well. The model instance is updated and has the new uid in the array, but once reloaded or reloading the user in a different action, it reverts to what it was before the add_friend method was called.
Using Rails 4.0.0.rc2 and pg 0.15.1
What could this be?
I suspect that ActiveRecord isn't noticing that your friends array has changed because, well, the underlying array reference doesn't change when you:
self.friends.push(target)
That will alter the contents of the array but the array itself will still be the same array. I know that this problem crops up with the postgres_ext gem in Rails3 and given this issue:
String attribute isn't marked as dirty, when it changes with <<
I'd expect Rails4 to behave the same way.
The solution would be to create a new array rather than trying to modify the array in-place:
update_attributes friends: self.friends + [ target ]
There are lots of ways to create a new array while adding an element to an existing array, use whichever one you like.
It looks like the issue might be your use of push, which modifies the array in place.
I can't find a more primary source atm but this post says:
One important thing to note when interacting with array (or other mutable values) on a model. ActiveRecord does not currently track "destructive", or in place changes. These include array pushing and poping, advance-ing DateTime objects. If you want to use a "destructive" update, you must call <attribute>_will_change! to let ActiveRecord know you changed that value.
If you want to use Postgresql array type, you'll have to comply with its format. From Postgresql docs the input format is
'{10000, 10000, 10000, 10000}'
which is not what friends.to_s will return. In ruby:
[1,2,3].to_s => "[1,2,3]"
That is, brackets instead of braces. You'll have to do the conversion yourself.
However I'd much rather rely on ActiveRecord serialize (see serialize). The database does not need to know that the value is actually an array, that's your domain model leaking into your database. Let Rails do its thing and encapsulate that information; it already knows how to serialize/deserialize the value.
Note: This response is applicable to Rails 3, not 4. I'll leave here in case it helps someone in the future.
I want to compare a param extracted from a link to the list of data present in column...
I am using pluck to generate a array of list(in controller) but not getting any success in comparing it
any method that I can fetch records in model and compare with param in controller or model
as passing controller instances in model seems inappropriate to me
Initially i am trying fetching and comparing in controller..
#abc=Group.pluck(:group_token)
what I tried to do before is defined group_fetch method in model and used it in controller to check condition but I was not able to compare param which comes frome url dynamically
def self.group_fetch
Group.find_by group_token: 'UuzsG7NMvYFzxwPDdYgLxJbF'
end
what will be the best way to fetch db column and compare it with the link param
You can use the exists? method which pretty much does what it says.
Group.exists?(group_token: params[:token])
You can use include? to check if the param is in the list. For example:
def your_method
list = Model.pluck(:attribute)
list.include?(params[:your_param])
end
I have a user model with a friends column of type text. This migration was ran to use the array feature with postgres:
add_column :users, :friends, :text, array: true
The user model has this method:
def add_friend(target)
#target would be a value like "1234"
self.friends = [] if self.friends == nil
update_attributes friends: self.friends.push(target)
end
The following spec passes until I add user.reload after calling #add_friend:
it "adds a friend to the list of friends" do
user = create(:user, friends: ["123","456"])
stranger = create(:user, uid: "789")
user.add_friend(stranger.uid)
user.reload #turns the spec red
user.friends.should include("789")
user.friends.should include("123")
end
This happens in development as well. The model instance is updated and has the new uid in the array, but once reloaded or reloading the user in a different action, it reverts to what it was before the add_friend method was called.
Using Rails 4.0.0.rc2 and pg 0.15.1
What could this be?
I suspect that ActiveRecord isn't noticing that your friends array has changed because, well, the underlying array reference doesn't change when you:
self.friends.push(target)
That will alter the contents of the array but the array itself will still be the same array. I know that this problem crops up with the postgres_ext gem in Rails3 and given this issue:
String attribute isn't marked as dirty, when it changes with <<
I'd expect Rails4 to behave the same way.
The solution would be to create a new array rather than trying to modify the array in-place:
update_attributes friends: self.friends + [ target ]
There are lots of ways to create a new array while adding an element to an existing array, use whichever one you like.
It looks like the issue might be your use of push, which modifies the array in place.
I can't find a more primary source atm but this post says:
One important thing to note when interacting with array (or other mutable values) on a model. ActiveRecord does not currently track "destructive", or in place changes. These include array pushing and poping, advance-ing DateTime objects. If you want to use a "destructive" update, you must call <attribute>_will_change! to let ActiveRecord know you changed that value.
If you want to use Postgresql array type, you'll have to comply with its format. From Postgresql docs the input format is
'{10000, 10000, 10000, 10000}'
which is not what friends.to_s will return. In ruby:
[1,2,3].to_s => "[1,2,3]"
That is, brackets instead of braces. You'll have to do the conversion yourself.
However I'd much rather rely on ActiveRecord serialize (see serialize). The database does not need to know that the value is actually an array, that's your domain model leaking into your database. Let Rails do its thing and encapsulate that information; it already knows how to serialize/deserialize the value.
Note: This response is applicable to Rails 3, not 4. I'll leave here in case it helps someone in the future.
I have a user model with a friends column of type text. This migration was ran to use the array feature with postgres:
add_column :users, :friends, :text, array: true
The user model has this method:
def add_friend(target)
#target would be a value like "1234"
self.friends = [] if self.friends == nil
update_attributes friends: self.friends.push(target)
end
The following spec passes until I add user.reload after calling #add_friend:
it "adds a friend to the list of friends" do
user = create(:user, friends: ["123","456"])
stranger = create(:user, uid: "789")
user.add_friend(stranger.uid)
user.reload #turns the spec red
user.friends.should include("789")
user.friends.should include("123")
end
This happens in development as well. The model instance is updated and has the new uid in the array, but once reloaded or reloading the user in a different action, it reverts to what it was before the add_friend method was called.
Using Rails 4.0.0.rc2 and pg 0.15.1
What could this be?
I suspect that ActiveRecord isn't noticing that your friends array has changed because, well, the underlying array reference doesn't change when you:
self.friends.push(target)
That will alter the contents of the array but the array itself will still be the same array. I know that this problem crops up with the postgres_ext gem in Rails3 and given this issue:
String attribute isn't marked as dirty, when it changes with <<
I'd expect Rails4 to behave the same way.
The solution would be to create a new array rather than trying to modify the array in-place:
update_attributes friends: self.friends + [ target ]
There are lots of ways to create a new array while adding an element to an existing array, use whichever one you like.
It looks like the issue might be your use of push, which modifies the array in place.
I can't find a more primary source atm but this post says:
One important thing to note when interacting with array (or other mutable values) on a model. ActiveRecord does not currently track "destructive", or in place changes. These include array pushing and poping, advance-ing DateTime objects. If you want to use a "destructive" update, you must call <attribute>_will_change! to let ActiveRecord know you changed that value.
If you want to use Postgresql array type, you'll have to comply with its format. From Postgresql docs the input format is
'{10000, 10000, 10000, 10000}'
which is not what friends.to_s will return. In ruby:
[1,2,3].to_s => "[1,2,3]"
That is, brackets instead of braces. You'll have to do the conversion yourself.
However I'd much rather rely on ActiveRecord serialize (see serialize). The database does not need to know that the value is actually an array, that's your domain model leaking into your database. Let Rails do its thing and encapsulate that information; it already knows how to serialize/deserialize the value.
Note: This response is applicable to Rails 3, not 4. I'll leave here in case it helps someone in the future.
I have a piece of controller code where some values are calculated. The result is in the form of an array of hashes. This needs to get into a partial form somehow so that it may be retrieved later during commit (which is through the Submit button).
The questions is how do we pass the array of hashes?
thanks.
Is there a reason it has to be through the form? This is the type of thing I usually use the session for.
I can't really think of a nice way to do what you're asking with forms. I guess you could create hidden fields for each key in your hash in the form with hidden_field_tag as an alternative. Then you run into problems translating it (what if a key's value is an array or another hash?).
You could easily store the hash in the session and then on each page load, check to see if there is a hash where you expect it. On calculating values:
session[:expected_info] = results
And each page load, something like this:
if session.has_key?(:expected_info)
results = session.delete(:expected_info)
# you already calculated the results, just grab them and
# do what you need to do
else
# you don't have the expected info
end
You should be able to pass it as a string to your partial:
[{}].inspect
and eval it when it is submitted back through the form:
eval("[{}]"))
but that would be really dirty…