I currently have 3 tables.
snippet
tags
snippet_tags
I'm using HABTM.
So I did a form to save a snippet with tags. Keywords are in a text field, separated by commas.
What I need to do is to take the string from this text field, loop on the keywords, check if they exist, if not create them, and THEN save the snippet.
I tried with a before_save but it doesn't seem to go by that way..
So if you could help me, it'd great!
Thanks a lot!
I think JosephL's answer is pretty good. Although, I would do it all in the snippets_controller action:
def create
#snippet = Snippet.new(params[:snippet])
#snippet.tags = params[:tags].split(',').collect { |tag| Tag.find_or_create_by_name(tag) }
if #snippet.save
# do something when successful
else
# do something when saving failed
end
end
Sorry for that long, one-line statement. ;-)
I didn't test the code, but I hope it works.
Tag.find_or_create_by_name will do exactly that: when a tag with that name exists, it will return it, otherwise it will create the new tag on the fly and return that.
This way, the tags are already saved, before you call #snippet.save.
Please note, that I just assumed, how your variables and parameters are named.
Here is a version of your create method. The main change is not creating a Snippet_Tag. If your HABTM association is set up correctly then your snippet will have a tags collection which you can add your tags to. The collection will be persisted as Snippet_Tags by ActiveRecord. See the rails associations guide for more details on HABTM associations.
def create
# Creating the snippet
#snippet = Snippet.new
#snippet.title = params[:snippet][:title]
#snippet.content = params[:snippet][:content]
# loop through the tags
params[:snippet][:tags].split(',').collect do |tag_string|
tag_string.strip!
if tag_string.length > 0
# Find or create tag
tag = Tag.find_or_create_by_name(tag_string)
# Add tag to tags collection
#snippet.tags << tag
end
end
if #snippet.save
# do something when successful
else
# do something when saving failed
end
end
Use split to break your string into an array of the tags
Find each tag by name
If not found then create the tag
Add the tag to the snippet
Save the snippet (in your controller)
Example method to put in your snippet model
def add_tags(tag_list_string)
tag_array = tag_list_string.split ','
tag_array.each do |tag_name|
tag = (Tag.find_by_name(tag_name) || Tag.create(:name => tag_name))
self.tags << tag
end
end
Try before_update?
Related
Is it possible to dynamically create key names of a hash? I'm passing the following hash parameters:
params[:store][:store_mon_open(5i)]
params[:store][:store_mon_closed(5i)]
params[:store][:store_tue_open(5i)]
params[:store][:store_tue_closed(5i)]
.
.
.
params[:store][:store_sun_open(5i)]
params[:store][:store_sun_closed(5i)]
To check if each parameter exists, I'm using two arrays:
days_of_week = [:mon, :tue, ..., :sun]
open_or_closed = [:open, :closed]
But, I can't seem to figure out how to dynamically create the params hash (the second key( with the array. Here's what I have so far:
days_of_week.each do |day_of_week|
open_or_closed.each do |store_status|
if !eval("params[:store][:store_#{day_of_week}_#{store_status}(5i)").nil
[DO SOMETHING]
end
end
end
I've tried a bunch of things including the eval method (as listed above) but rails seems to dislike the parentheses around the "5i". Any help is greatly appreciated!
You should be able to do
if params[:store]["store_#{day_of_week}_#{store_status}(5i)".to_sym]
Note that you were missing the ? on .nil? and that !object.nil? can be shortened to just object
Assuming this is a HashWithIndifferentAccess, you should be able to access it via string just as you could with a symbol. Thus:
days_of_week.each do |day_of_week|
open_or_closed.each do |store_status|
key = "store_#{day_of_week}_#{store_status}(5i)"
unless params[:store][key]
# DO SOMETHING
end
end
end
If it's not a HashWithIndifferentAccess then you should just be able to call key.to_sym to turn it into a symbol.
I have an array with model attributes (these model attributes are columns in DB table). I am trying to iterate through this array and automatically create a record which I would like to save to DB table, something like this:
columns.each_with_index do |c, i|
user.c = data[i]
puts user.c
end
user is model.
But if I try the snippet above, I get
undefined method `c=' for #<User:0x007f8164d1bb80>
I've tried also
columns.each_with_index do |c, i|
user."#{c}" = data[i]
puts user."#{c}"
end
But this doesn't work as well.
Data in columns array are taken from form that sends user, so I want to save only data that he send me, but I still cannot figure it out...
I would like to ask you for help... thank you in advance!
user.send("#{c}=".to_sym, data[i])
Also, you can access the attributes as a hash.
user.attributes[c] = data[i]
The best thing would probably be to build a hash and to use update_attributes:
mydata = {}
columns.each_with_index{|c, i| mydata[c] = data[i]}
user.update_attributes(mydata)
this way you retain the protections provided by attr_accessible.
If this is actually in a controller, you can just make use of some basic rails conventions and build the User record like this:
#user = User.new(params[:user])
if #user.save
# do something
else
# render the form again
end
Although you can set the values using send, I agree with #DaveS that you probably want to protect yourself via attr_accessibles. If your planning to use Rails 4, here's a good overview.
My Tag model has some validations for attribute name. It's wroking fine in all other cases. But when I call find_or_create_by_name by this way:
# The last value of this string (by coma) is empty.
# So this record should not be saved.
tags_line = 'ruby, javascript, '
tags_line.split(',').each do |tag_name|
tag = Tag.find_or_create_by_name(tag_name.strip) do |new_tag|
new_tag.update_attribute :user_id, member.user.id
end
# Just append tag to this model
# through `has_and_belongs_to_many :tags`
tags << tag if tag
end
But this empty tag is even being saved. So, can be something wrong in this code?
NOTE: When I remove the block, it works:
...
tags_line.split(',').each do |tag_name|
# This way, invalid tag will not be created.
tag = Tag.find_or_create_by_name(tag_name.strip)
tags << tag if tag
end
The problem was caused by the fact that attribute :name is protected in model. So, as the Rails Doc says:
The same dynamic finder style can be used to create the object if it
doesn’t already exist. This dynamic finder is called with
find_or_create_by_ and will return the object if it already exists and
otherwise creates it, then returns it. Protected attributes won’t be
set unless they are given in a block.
The fixed code is...
tags_field.split(',').each do |tag_name|
tag_name.strip!
tag = General::Tag.find_or_create_by_name(tag_name) do |new_tag|
# :name is attr_protected
new_tag.name = tag_name
new_tag.user_id = member.user.id
end
tags << tag if tag
end
or you could use the following type of method chaining if you rather skip the block
tags_field.split(',').each do |tag_name|
tag_name.strip!
tag = General::Tag.find_or_create_by_name_and_user_id(tag_name, member.user.id)
tags << tag if tag
end
In the last iteration tag has the string ' ', that is, a whitespace.
Try
tags << tag unless tag.strip.blank?
as long as you're working with Ruby On Rails, which I suspect, as blank? is a rails extension.
Else
tags << tag unless tag.strip.empty?
Edit:
update_attribute does not run validations. See this
I have this code that's working:
case index
when "Books"
#reading_img = res.items.first.get_hash('MediumImage')["URL"] # don't show an image
#reading_link = create_amz_url(search, asin)
tempy = #nowreading.content.match(/#nowreading.*/).to_s.gsub("#nowreading",'') # strips away the #nowreading tag
#nowreading.content = tempy.match(/#{search}.*/).to_s.gsub("#{search}", #reading_link)
# replaces the book title (passed in as variable 'search') with the URL'ed title
when "Music"
#listening_img = res.items.first.get_hash('MediumImage')["URL"] # don't show an image
#listening_link = create_amz_url(search, asin)
tempy = #nowlistening.content.match(/#nowlistening.*/).to_s.gsub("#nowlistening",'') # strips away the #nowreading tag
#nowlistening.content = tempy.match(/#{search}.*/).to_s.gsub("#{search}", #listening_link)
# replaces the song title (passed in as variable 'search') with the URL'ed title
end
I need to repeat this for many, many categories. I tried something like this to DRY the code but it didn't work:
def get_img_and_links(act, res, search, asin)
'#'+act+'ing_img' = res.items.first.get_hash('MediumImage')["URL"] # don't show an image
'#'+act+'ing_link' = create_amz_url(search, asin)
tempy = '#now'+act+'ing'.content.match(/#now"#{act}"ing.*/).to_s.gsub("#now#{act}ing",'') # strips away the #nowreading tag
'#now'+act+'ing'.content = tempy.match(/#{search}.*/).to_s.gsub("#{search}", '#'+act+'ing_link')
# replaces the book title (passed in as variable 'search') with the URL'ed title
end
Essentially, I was trying to create a function that took an "act" (e.g., "read", "listen", etc) and have the variables within that function be dynamic. Can this be accomplished? If so, how?
Look up instance_variable_set here: http://ruby-doc.org/core/classes/Object.html. It's what you need to dynamically create these variables.
instance_variable_set "##{act}ing_img".to_sym, res.items.first.get_hash('MediumImage')["URL"]
And so on...
Good looking out trying to dry up your code. I would definitely use some hashes there instead of instance variables. Then you can use the key as the action. Just a thought.
IMPO, I think you should use more generic variables. Although the creating variables are supported by ruby, it will make your code hard to read
my suggestion is having some generic names like
#img (instead of reading_img, listing_img etc..)
#link (instead of reading_link, listing_link etc..)
and something like #content, because as your login at a given time only one will be selected and this wouldn't be a problem
Again, this is what i understood from the code you posted and correct me if I'm wrong
cheers
sameera
you should do something like this:
def setup
#reading_img = get_img(whatever)
#reading_link = get_link(whatever)
#listening_img = get_img(whatever)
#listening_link = get_link(whatever)
end
def get_img(whatever)
...do stuff and return stuff...
end
def get_link(whatever)
...do stuff and return stuff...
end
I've been implementing some nice interactive interfaces that can sort lists in my m rails app for models that use acts_as_list. I have a sort function that gets called and sets the position for each record afterr each drag and drop using the sortable_element script.aculo.us function.
This is an example of the controller action that handles the sort after the drag and drop completes:
def sort
params[:documents].each_with_index do |id, index|
Document.update_all(['position=?', index+1], ['id=?', id])
end
end
Now I am trying to do this same thing with a model that is a nested set (acts_as_nested_set). An example of the type of interface interaction: http://script.aculo.us/playground/test/functional/sortable_tree_test.html
I am stuck on how to write the controller action to handle the sort when the drag and drop completes.
I've added the :tree=>true parameter to the sortable _element function so far which appears to send a list of hashes but it seems that I am still missing information about the entire nested order....
I was certain this has been done before and didn't want to try to reinvent the wheel, but I can't seem to find any examples of the controller action <-> view with js function setup to handle a sortable acts_as_nested_set
Any help with creating an interactive sortable nested set in rubyonrails would be appreciated!
Thanks,
John
a good solution with ONE sql-query from http://henrik.nyh.se/2008/11/rails-jquery-sortables
# in your model:
def self.order(ids)
update_all(
['ordinal = FIND_IN_SET(id, ?)', ids.join(',')],
{ :id => ids }
)
end
see example app here - http://github.com/matenia/jQuery-Awesome-Nested-Set-Drag-and-Drop
It's a hacky way of doing it, but its basically, sort first, then save order.
Uses nestedsortables, serializelist, and 2 actions to traverse the tree
PS: I know this question is over a year old but hoping that the link above helps someone else coming here.
edit: added Rails3 example with some slightly cleaner code.
Just found this:
sortable_element_for_nested_set on github
Looks like it'll do the job, however I'm having some bugs while trying to implement it. It basically makes the javascript return the id of the element that was moved, then goes through the elements and returns its new parent, left and right values. Can't believe it's taken this long for something like this to be written! Lucky it was just when I needed it :)
Here's a snippet of code from my project that does the trick:
http://gist.github.com/659532
def reorder_children(ordered_ids)
ordered_ids = ordered_ids.map(&:to_i)
current_ids = children.map(&:id)
unless current_ids - ordered_ids == [] && ordered_ids - current_ids == []
raise ArgumentError, "Not ordering the same ids that I have as children. My children: #{current_ids.join(", ")}. Your list: #{ordered_ids.join(", ")}. Difference: #{(current_ids - ordered_ids).join(', ')} / #{(ordered_ids - current_ids).join(', ')}"
end
j = 0
transaction do
for new_id in ordered_ids
old_id = current_ids[j]
if new_id == old_id
j += 1
else
Category.find(new_id).move_to_left_of(old_id)
current_ids.delete(new_id)
end
end
end
end
You call it on the parent, and it'll sort the children.
You just pass in the value that you get from Sortable, like so:
def reorder
#category.reorder_children(params[:categories])
render :nothing => true
end
Hope this helps.
//Lars
the_sortable_tree
Sortable Nested Set for Rails 3.1+
Dreaming about Drag and Drop for Nested Sets? It’s should be with JQuery?
Here’s the solution!