How to make a serialized column empty? - ruby-on-rails

I have a column in Company, that is serialized as Array:
class Company
serialize :column_name, Array
end
In rails console, when I try the following:
existing = Company.last.column_name
# it gives a array with single element, say [1]
existing.delete(1)
# now existing is []
Company.last.update_attributes!(column_name: existing)
# It empties the array, so:
Company.last.column_name #gives []
But, when I try the same code in remove method of some controller, it never removes the last element. It always returns [1].
How can I empty the serialized column?
NOTE:- This logic works when I have multiple elements, but doesn't work for the last element alone.
CONTROLLER CODE
def remove_restricted_num
company = Company.where(id: params[:id]).first
restricted_numbers = company.restricted_numbers
num = params[:num].to_i
if restricted_numbers.include?(num)
restricted_numbers.delete(num)
company.update_attributes!(restricted_numbers: restricted_numbers)
render js: "alert('#{num} was removed')"
else
render js: "alert('Number not found in the list')"
end
end

I got the fix, we need to use dup to get a independent variable existing, which otherwise was referencing Company.last.column_name
existing = Company.last.column_name.dup # THIS WORKS!!!
existing.delete(1)
Company.last.update_attributes!(column_name: existing)
This updates the column to [], as I need.

Related

Rails.cache.fetch not working when model given as key

I was under the impression that we could give Rails a model (anything responding to to_param or cache_key) to Rails.cache.fetch and it would create a key and cache the response of the block.
I have this code:
class ResultsPresenter
def initialize(project)
#project = project
end
def results
results = Rails.cache.fetch("#{#project}/results") do
sleep 3
a = long_running_query
b = another_long_query
c = a + b
end
end
end
# called
project = Project.find(params[:project_id]_
presenter = ResultsPresenter.new(project)
presenter.results
#project was passed to ResultsPresenter and is an ActiveRecord model. When I specify "#{#project.to_param}/results" or "#{#project.cache_key}/results" everything works just fine. I also checked if the #project was being updated but it's not.
Anyone know why it does not take an ActiveRecord model?
You want your cache key to be an array, probably Rails.cache.fetch([#project, 'results']).
This will give a cache key along the lines of "project/5-20190812000000/results". The format for the model is "model_name/model_id-updated_at" with the rest of the array values joined with /.
If you were to look at the key generated from your example, it would look something like "#<Project:0x007fbceaadbbc90>/results". This is happening because you are baking the value of #project.to_s into the value of the key you are passing into fetch.

Passing Parameters to Active Admin Table_for columns

I'm trying to set up a table_for in my active admin project which displays information based on methods I pass to it. I'm setting it up so that in the model, there is an array of arrays. The arrays within the array contain first the label, then the method meant to be run in the column. I'm trying to set it up this way:
panel "Acquired Shares" do
table_for shareholder.acquired_shares_transactions do
shareholder.acquired_shares_info.each do |section|
column (section[0]) { |transaction| section[1] }
end
end
end
Here is the code of the method which returns the array of arrays:
def acquired_shares_info
data = [[:label, transaction.event.to_s], [:amount_of_shares, transaction.amount_of_shares],
[:share_price, transaction.event.share_price],
[:total_price, (transaction.amount_of_shares * transaction.event.share_price)],
[:occurred_on, transaction.event.occurred_on],
[:from_shareholder, transaction.event.from_shareholder],
[:share_transaction_action, transaction.event.share_transaction_action.name],
[:share_transaction_type, transaction.event.share_transaction_type.name]]
return data
end
This is all is meant to create a column for each label and method I specify in the array. However, I am stuck on how to pass the labels and methods from the array into the column for the table. The way that I try here keeps throwing the error "No block given" on the array. Anyone have ideas on how to set this up?
Thank you!
Defer evaluation of the method by wrapping it in a proc. Pass the proc as the last parameter instead of specifying a block, Ruby will substitute it for you.
data = [[:label, proc { |transaction| transaction.event.to_s }], ...
...
column section[0], section[1]
...

Search for an object in rails by an array and create objects if not exist

I want to call the ActiveRecord method where with an array for a column. If the each item on the array doesn't exist, create the object. The closest method I found for this is first_or_create but this seems to be called only once, not for each time the record doesn't exist. Below is my example code-
hashtag_list = params[:message][:hashtag_primary]
#hashtags = Hashtag.where({:name => hashtag_list}).first_or_create do |hashtag|
hashtag.creator = current_user.id
end
Rails version- 4.2.1
I don't know a direct method, only a workaround
existing_tags = Hashtag.where({:name => hashtag_list}).pluck(:name)
not_existing_tags = hashtag_list - existing_tags
#hashtags = Hashtag.where({:name => existing_tags}).all
not_existing_tags.each do |tag|
#hashtags << Hashtag.new name: tag
end
#hashtags.each do |hashtag|
hashtag.creator = current_user.id
end
This is expected behavior of where + first_or_create method. Basically where(field: array) produces an SQL to find all records where field matches any item in the array. Than you have first_or_create method which takes the first record from results or creates a new one with escaped array value assigned to a field (so something like field: "[\"foo\", \"bar\"]" when used as where(field: %w(foo bar)).
If you want to create records for each hashtag from your list, you should iterate over it:
if #hashtag = Hashtag.where({:name => hashtag_list}).first
# do something if found the first one
else
hashtag_list.each do |hashtag|
# create an object
end
end
If you want to create missing hashtags even if the record is found, you can extract this to a private helper method with missing tags as the argument and re-write code as:
if #hashtags = Hashtag.where({:name => hashtag_list})
# do something if found
end
create_missing_hashtags(hashtag_list - #hashtags.pluck(:name))

Rails and Mongoid :: Converting Strings to Arrays

I collect tags in a nice javascript UI widget. It then takes all the tags and passes them to the server as tag1,tag2,tag3,etc in one text_field input. The server receives them:
params[:event][:tags] = params[:event][:tags].split(',') # convert to array
#event = Event.find(params[:id])
Is there a better way to convert the string to the array? It seems like a code smell. I have to put this both in update and in the new actions of the controller.
you could do this in the model:
I have seldom experience on mongoid. The following would work in active record (the only difference is the write_attribute part)
class Event
def tags=(value_from_form)
value_from_form = "" unless value_from_form.respond_to(:split)
write_attribute(:tags, value_from_form.split(','))
end
end
On the other hand, for consistency, you may want to do the following:
class Event
def tags_for_form=(value_from_form)
value_from_form = "" unless value_from_form.respond_to(:split)
self.tags = value_from_form.split(',')
end
def tags_for_form
self.tags
end
# no need to change tags and tags= methods. and tags and tags= methods would return an array and accept an array respectively
end
In the first case (directly overwriting the tags= method), tags= accepts a string but tags returns an array.
In the second case, tags_for_form= and tags_for_form accepts and returns string, while tags= and tags accepts and returns array.
I just create another model attribute that wraps the tags attribute like so:
class Event
def tags_list=(tags_string)
self.tags = tags_string.split(',').map(&:strip)
end
def tags_list
self.tags.join(',')
end
end
In your form, just read/write the tags_list attribute which will always accept, or return a preformated string. (The .map(:strip) part simply removes spaces on the ends in case the tags get entered with spaces: tag1, tag2, tag3.
PeterWong's answer misses the '?' from the respond_to() method;
class Event
def tags=(value_from_form)
value_from_form = "" unless value_from_form.respond_to?(:split)
write_attribute(:tags, value_from_form.split(','))
end
end

rails process for saving order of awesome_nested_set using jquery & nestedsortables

I have a categories model made with the fantastic awesome_nested set.
I have successfully generated the drag and drop tree and successfully generated the complete hash of this tree using SERIALIZELIST plugin and sent it to the "array" method that I have added to my categories controller. (using jquery and nestedsortables)
The hash from my log looks like so ...
Processing CategoriesController#array
(for 127.0.0.1 at 2010-08-19 23:12:18)
[POST] Parameters:
{"ul"=>{"0"=>{"class"=>"",
"id"=>"category_1",
"children"=>{"0"=>{"class"=>"",
"id"=>"category_4",
"children"=>{"0"=>{"class"=>"",
"id"=>"category_3"}}}}},
"1"=>{"class"=>"", "id"=>"category_2",
"children"=>{"0"=>{"class"=>"",
"id"=>"category_5"},
"1"=>{"class"=>"",
"id"=>"category_6"}}}}}
i'm just having trouble with the sort function.
Awesome nested set does provide a few move functions but I can't seem to get my head around it.
I want to do something like this when the user hits save (btw it does an ajax request and passes the above data correctly)
def array
newlist = params[:ul]
newlist.each_with_index do |id, index, children|
#insert code here for saving the re-ordered array
end
render :nothing => true
end
I hope this is enough information and hope someone can answer this question.
Cheers,
Matenia
----------- UPDATE AND PROGRESS -----------
Since posting this a few days ago, I mucked around with the logger.info in my dev environment to see what was going on behind the scenes.
I ended up writing 2 functions. One to go through the roots of the array and the other to recursively move the children and childrens children into place. But this ends up with too many database calls (there may be no other way to do it though).
the code looks like so ...
def array
# fetch the current tree
#allcategories = Category.all
# assign the sorted tree to a variable
newlist = params[:ul]
# initialize the previous item
previous = nil
#loop through each item in the new list (passed via ajax)
newlist.each_with_index do |array, index|
# get the category id of the item being moved
moved_item_id = array[1][:id].split(/category_/)
# find the object that is being moved (in database)
#current_category = Category.find_by_id(moved_item_id)
# if this is the first item being moved, move it to the root.
unless previous.nil?
#previous_item = Category.find_by_id(previous)
#current_category.move_to_right_of(#previous_item)
else
#current_category.move_to_root
end
# then, if this item has children we need to loop through them
unless array[1][:children].blank?
# unless there are no children in the array, send it to the recursive children function
childstuff(array[1], #current_category)
end
# set previous to the last moved item, for the next round
previous = moved_item_id
end
render :nothing => true
end
def childstuff(node, category)
# find the category that has been passed into the function
#selected_category = Category.find(category)
for child in node[:children]
child_id = child[1][:id].split(/category_/)
child_category = Category.find_by_id(child_id)
child_category.move_to_child_of(#selected_category)
#if this child has children -- run recursion on this function
unless child[1][:children].blank?
childstuff(child[1], child_category)
end
end
end
I hope someone can shed some light on how to make this more efficient and how to reduce the number of database calls. I have thought about writing other functions, but theyre all going to do the same thing.
For this particular project, I don't believe there would be more than 100 different categories. It's not the best way, but it works.
Cheers again,
Matenia
THE FINAL WORKAROUND
I had an issue with the above code where it wasn't saving the children properly.
Here is my latest attempt, which seems to work well.
def array
# assign the sorted tree to a variable
newlist = params[:ul]
# initialize the previous item
previous = nil
#loop through each item in the new list (passed via ajax)
newlist.each_with_index do |array, index|
# get the category id of the item being moved
moved_item_id = array[1][:id].split(/category_/)
# find the object that is being moved (in database)
#current_category = Category.find_by_id(moved_item_id)
# if this is the first item being moved, move it to the root.
unless previous.nil?
#previous_item = Category.find_by_id(previous)
#current_category.move_to_right_of(#previous_item)
else
#current_category.move_to_root
end
# then, if this item has children we need to loop through them
unless array[1][:children].blank?
# NOTE: unless there are no children in the array, send it to the recursive children function
childstuff(array[1], #current_category)
end
# set previous to the last moved item, for the next round
previous = moved_item_id
end
Category.rebuild!
render :nothing => true
end
def childstuff(mynode, category)
# logger.info "node = #{node} caegory = #{category}"
#loop through it's children
for child in mynode[:children]
# get the child id from each child passed into the node (the array)
child_id = child[1][:id].split(/category_/)
#find the matching category in the database
child_category = Category.find_by_id(child_id)
#move the child to the selected category
child_category.move_to_child_of(category)
# loop through the children if any
unless child[1][:children].blank?
# if there are children - run them through the same process
childstuff(child[1], child_category)
end
end
end
still too many database calls, but I guess that's the price to pay for wanting this functionality as it needs to re-record each item in the database.
Hope this helps someone else in need.
Feel free to msg me if anyone wants help with this.
AWESOME NESTED SET + JQUERY DRAG AND DROP + SERIALIZELIST PLUGIN ....
Cheers,
Matenia
see edited question above for final workaround ..I have posted the code on github .. although it may have a few bugs and needs refactoring BADLY!
JQUERY NESTED SORTABLES - DRAG AND DROP - AWESOME NESTED SET
UPDATE: Added rails 3 example to repo with slightly cleaner code
The same issue came up when upgrading a rails 2.3 app to 3.1. In my case, I only wanted to sort one depth (root or not). Here's what I ended up with:
# Fetch all IDs (they will be in order)
ids = params[:sort].collect { |param| param[/^page_(\d+)$/, 1] }
# Remove first item from array, moving it to left of first sibling
prev = Page.find(ids.shift)
prev.move_to_left_of(prev.siblings.first)
# Iterate over remaining IDs, moving to the right of previous item
ids.each_with_index { |id, position| current = Page.find(id); current.move_to_right_of(prev); prev = current }

Resources