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
Related
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))
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 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
I have a MongoMapper model and am trying to convert a comma-delimited string into an Array to be stored.
The main problem is that a string such as tags = "first,second,third" is not getting converted to an array in the database like ["first","second","third"]. Instead it is going in as ["first,second,third"].
There are some other strange things going on as well:
1) In preen_tags I have to include the unless tags.nil? after every line
2) in preen_tags, using the debugger tags returns nil
Here is my model
class Template
include MongoMapper::Document
validate :validate_tags
after_validation :preen_tags
key :template_id, ObjectId
key :title, String
key :description, String
key :tags, Array
timestamps!
def validate_tags
errors.add_to_base "You Must Enter At Least 1 Tag." if tags.blank?
end
def preen_tags
#return if tags.nil? #why doesn't this work??
#only alphanumeric chars allowed, except hyphens and commas
tags = tags[0] if tags.is_a?(Array)
tags = tags.gsub(/[^0-9a-z\-\,]/i, '') unless tags.nil?
#convert spaces to hyphens
tags = tags.gsub(/\s/, '-') unless tags.nil?
tags = tags.split(",") unless tags.nil?
end
end
it's because by default tags is an Array in MongoMapper like you define it. So you can try tags.empty? instead of tags.nil?
In last case tags becomes nil because you try get first element of tags, but there are no one inside. Just nil. You tags becomes nil.
Looks like converting the String to an Array inside the controller before passing it to the Model has solved things.
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?