As an extension of the question
How to retrieve the hash values in the views in rails
I have some doubts of keeping hash values in the table..
I have a user detail table where i am maintaining the additional details of the user in a column named additional_info in a hash format .. Will it be good in keeping like so...
As if the user scenario changes if the user wants to find all the users under a particular project where i kept the project to which the user belongs in the hash format..
Give some suggestions..
Simple solution is to serialiaze it:
class FooBar < ActiveRecord::Base
# ...
serialize :additional_info
#...
end
This internally uses the YAML serializer. You can assign any object that can be serialized using YAML.
foo = FooBar.first
foo.additional_info = {:foo => 'Lorem', :bar => 'ipsum'}
foo.save
foo.additional_info[:foo] # Gives 'Lorem'
Related
I have a model:
class A < ActiveRecord::Base
has_many :B
end
And I want to reset or update A's B association, but only save it later:
a = A.find(...)
# a.bs == [B<...>, B<...>]
a.bs = []
#or
a.bs = [B.new, B.new]
# do some validation stuff on `a` and `a.bs`
So there might be some case where I will call a.save later or maybe not. In the case I don't call a.save I would like that a.bs stay to its original value, but as soon as I call a.bs = [], the old associations is destroyed and now A.find(...).bs == []. Is there any simple way to set a record association without persisting it in the database right away? I looked at Rails source and didn't find anything that could help me there.
Thanks!
Edit:
I should add that this is for an existing application and there are some architecture constraint that doesn't allow us to use the the regular ActiveRecord updating and validation tools. The way it works we have a set of Updater class that take params and assign the checkout object the value from params. There are then a set of Validater class that validate the checkout object for each given params. Fianlly, if everything is good, we save the model.
In this case, I'm looking to update the association in an Updater, validate them in the Validator and finally, persist it if everything check out.
In summary, this would look like:
def update
apply_updaters(object, params)
# do some stuff with the updated object
if(validate(object))
object.save(validate: false)
end
Since there are a lot of stuff going on between appy_updaters and object.save, Transaction are not really an option. This is why I'm really looking to update the association without persisting right away, just like we would do with any other attribute.
So far, the closest solution I've got to is rewriting the association cache (target). This look something like:
# In the updater
A.bs.target.clear
params[:bs].each{|b| A.bs.build(b)}
# A.bs now contains the parameters object without doing any update in the database
When come the time to save, we need to persist cache:
new_object = A.bs.target
A.bs(true).replace(new_object)
This work, but this feel kind of hack-ish and can easily break or have some undesired side-effect. An alternative I'm thinking about is to add a method A#new_bs= that cache the assigned object and A#bs that return the cached object if available.
Good question.
I can advice to use attributes assignment instead of collection manipulation. All validations will be performed as regular - after save or another 'persistent' method. You can write your own method (in model or in separated validator) which will validate collection.
You can delete and add elements to collection through attributes - deletion is performed by additional attribute _destroy which may be 'true' or 'false' (http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html), addition - through setting up parent model to accept attributes.
As example set up model A:
class A < ActiveRecord::Base
has_many :b
accepts_nested_attributes_for :b, :allow_destroy => true
validates_associated :b # to validate each element
validate :b_is_correct # to validate whole collection
def b_is_correct
self.bs.each { |b| ... } # validate collection
end
end
In controller use plain attributes for model updating (e.g update!(a_aparams)). These methods will behave like flat attribute updating. And don't forget to permit attributes for nested collection.
class AController < ApplicationController
def update
#a = A.find(...)
#a.update(a_attributes) # triggers validation, if error occurs - no changes will be persisted and a.errors will be populated
end
def a_attributes
params.require(:a).permit([:attr_of_a, :b_attributes => [:attr_of_b, :_destroy]])
end
end
On form we used gem nested_form (https://github.com/ryanb/nested_form), I recommend it. But on server side this approach uses attribute _destroy as mentioned before.
I finally found out about the mark_for_destruction method. My final solution therefor look like:
a.bs.each(&:mark_for_destruction)
params[:bs].each{|b| a.bs.build(b)}
And then I can filter out the marked_for_destruction? entry in the following processing and validation.
Thanks #AlkH that made me look into how accepts_nested_attributes_for was working and handling delayed destruction of association.
Suppose I deleted a document or subdocument in mongodb. Can I create document / subdocument with the same _id as the deleted one? In this case, we assume, we cannot do update operation, just delete and create.
For example using Mongoid (Rails gem for mongodb) :
We have Person Model
class Person
include Mongoid::Document
field :a, :type => String
embeds_many :personattributes
end
class Personattribute
include Mongoid::Document
field :myattribute, :type => String
embedded_in :person
end
And in my Rails controller
class MyController < ApplicationController
...
#the_attributes=#person.personattributes.entries
...
#controller will render page, an instance variable #the_attributes will be available as JSON in clientside
end
Then user does some client side data modifications. They can add 1 or more personattributes to that person data. They can do some changes on its attributes. They can delete some also.
All in client side.
Then by AJAX call, user sends the modified data back in JSON format like
[{_id:"5253fd494db79bb271000009",myattribute:"test"},{...},...]
The retriever in controller retrieves the data
Then totally replace the attribute list inside person with the new one. Total deletion and insertion, no update.
class MyController < ApplicationController
...
#person.personattributes.delete_all #delete all attributes a #person has
attributes=params[:attributes]
attributes.map {|attr|
Personattribute.new(:_id => Moped::BSON::ObjectId.from_string(attr["_id"].to_s), :myattribute => attr["myattribute"])
}
#person.personattributes=attributes
#person.save
...
end
Can I do this? It simply means, delete all, and insert all and reuse the _ids.
If not, I will be happy to get some advice on a better approach on this.
I can't do upsert since the deleted documents will need another loop to handle.
Thank you
Yes, you can do it but I would recommend you not to do that. It seems to have lots of security issues if someone modifies the array manually
I could send:
[{_id:"5253fd494db79bb271000009",myattribute:"test_modified"},{...},...]
or even:
[{_id:"my_new_id_1",myattribute:"test_modified"},{...},...]
which would raise an exception
Moped::BSON::ObjectId.from_string "my_new_id_1" #=> raises an Exception
Try something like:
attributes=params[:attributes]
attributes.each do |attr|
#person.personattributes.find(attr["_id"]).myattribute = attr["myattribute"]
#or #person.personattributes.find(attr["_id"]).try(:myattribute=,attr["myattribute"])
end
Probably in a future you want to change the action and send just the modified personattributes in the array instead of all the personattributes. What would you do then if you delete_all and rebuild personattributes with just the sent personattributes?
EDIT
This handles personattributes updates. Create or delete personattributes should go in different actions:
Create action
#person.personattributes.push Personattribute.new(my_attribute: params[:my_attribute])
Delete action
#person.personattributes.delete(params[:personattribute_id])
Yes, you can keep using the same _id. They just need to be unique within a collection -- and that's only true for the document's _id.
Any ObjectId you might use elsewhere in an another field in a document (or in a subdocument) doesn't need to be unique, unless you've created an index where it must be unique.
New to Rails and Ruby and trying to do things correctly.
Here are my models. Everything works fine, but I want to do things the "right" way so to speak.
I have an import process that takes a CSV and tries to either create a new record or update an existing one.
So the process is 1.) parse csv row 2.) find or create record 3.) save record
I have this working perfectly, but the code seems like it could be improved. If ParcelType wasn't involved it would be fine, since I'm creating/retrieving a parcel FROM the Manufacturer, that foreign key is pre-populated for me. But the ParcelType isn't. Anyway to have both Type and Manufacturer pre-populated since I'm using them both in the search?
CSV row can have multiple manufacturers per row (results in 2 almost identical rows, just with diff mfr_id) so that's what the .each is about
manufacturer_id.split(";").each do |mfr_string|
mfr = Manufacturer.find_by_name(mfr_string)
# If it's a mfr we don't care about, don't put it in the db
next if mfr.nil?
# Unique parcel is defined by it's manufacturer, it's type, it's model number, and it's reference_number
parcel = mfr.parcels.of_type('FR').find_or_initialize_by_model_number_and_reference_number(attributes[:model_number], attributes[:reference_number])
parcel.assign_attributes(attributes)
# this line in particular is a bummer. if it finds a parcel and I'm updating, this line is superfulous, only necessary when it's a new parcel
parcel.parcel_type = ParcelType.find_by_code('FR')
parcel.save!
end
class Parcel < ActiveRecord::Base
belongs_to :parcel_type
belongs_to :manufacturer
def self.of_type(type)
joins(:parcel_type).where(:parcel_types => {:code => type.upcase}).readonly(false) unless type.nil?
end
end
class Manufacturer < ActiveRecord::Base
has_many :parcels
end
class ParcelType < ActiveRecord::Base
has_many :parcels
end
It sounds like the new_record? method is what you're looking for.
new_record?() public
Returns true if this object hasn’t been saved yet — that is, a record
for the object doesn’t exist yet; otherwise, returns false.
The following will only execute if the parcel object is indeed a new record:
parcel.parcel_type = ParcelType.find_by_code('FR') if parcel.new_record?
What about 'find_or_create'?
I have wanted to use this from a long time, check these links.
Usage:
http://rubyquicktips.com/post/344181578/find-or-create-an-object-in-one-command
Several attributes:
Rails find_or_create by more than one attribute?
Extra:
How can I pass multiple attributes to find_or_create_by in Rails 3?
I have two tables:
stores
raw_stores_data
The raw_stores_data is received from a third party daily.
I'd update certain fields of the stores model if those fields have been modified for that record in raw_stores_data.
Currently I have a bunch of conditional statements that check each of those fields. Is there any better way to code this?
new_data = raw_stores_data.all.select do |item|
item.store_id.present?
end
new_data.each do |item|
if item.field1 != item.stores.field1
...
...
...
# update record with hash of fields to update created above
end
You could add an association and special mutators to the 'raw' model that know how manipulate the 'stores' object. This serves to keep the model code in the model. Thin controller, comprehensive models, etc.
class Store < ActiveRecord::Base
has_one :raw_stores_data
end
class RawStoresData < ActiveRecord::Base
belongs_to :store
def field1=(value)
store.field1 = value
store.save!
field1 = value
end
end
I'm hand waving at some of the details, and you might want to reverse the direction of the association or make it go both directions.
EDIT:
You would use this as such:
raw_data = RawStoreData.find(param[:id]) # or new or however you get this object
raw_data.field1 = param[:field1]
The act of assigning will use the 'field1=' method, and make the change to the associated store object. If you're worried about saving unnecessarily, you could conditionalize in that method to only save if the value changed.
I hope this is clearer.
I have an Appointment model, whose available_dates can include multiple dates (say, available in Jan 1, 5, 6 and 7).
My question is very basic: how should I store available dates for each event?
What I can think of is a table Avaiable_Dates with two columns: event_id and date. Each event would have multiple rows, one date per row. It seems to be cumbersome to query entire table to make sure we got all dates of an event. A Hash {event => {date1, date2, date3}} would be faster, but I don't know how to implement it using ActiveRecord.
Thank you.
It might not be a bad idea to just use the separate model for available times, but if you decide to go the hash route you can do so using the serialize keyword. You have to tell ActiveRecord to serialize the variable, and then it will do the serialization and deserialization automatically whenever you access the hash.
Saving arrays, hashes, and other non-mappable objects in text columns
Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method serialize. This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work.
class User < ActiveRecord::Base
serialize :preferences
end
user = User.create(:preferences => { "background" => "black", "display" => large })
User.find(user.id).preferences # => { "background" => "black", "display" => large }
You can also specify a class option as the second parameter that’ll raise an exception if a serialized object is retrieved as a descendant of a class not in the hierarchy.
class User < ActiveRecord::Base
serialize :preferences, Hash
end
user = User.create(:preferences => %w( one two three ))
User.find(user.id).preferences # raises SerializationTypeMismatch
When you specify a class option, the default value for that attribute will be a new instance of that class.
class User < ActiveRecord::Base
serialize :preferences, OpenStruct
end
user = User.new
user.preferences.theme_color = "red"
From rubyonrails.org
From a design perspective, if you think you will ever add any more data to the available time object then you should make it its own model. Otherwise a serialized hash seems fine.
I don't see a problem with the separate table that you mentioned, I would go with that. It will also be easier to extend later which you will appreciate when the time comes.