How do I reject if exists? for non-nested attributes? - ruby-on-rails

Currently my controller lets a user submit muliple "links" at a time. It collects them into an array, creates them for that user, but catches any errors for the User to go back and fix. How can I ignore the creation of any links that already exist for that user? I know that I can use validates_uniqueness_of with a scope for that user, but I'd rather just ignore their creation completely. Here's my controller:
#links =
params[:links].values.collect{ |link|
current_user.links.create(link)
}.reject { |p| p.errors.empty? }
Each link has a url, so I thought about checking if that link.url already exists for that user, but wasn't really sure how, or where, to do that. Should I tack this onto my controller somehow? Or should it be a new method in the model, like as in a before_validation Callback? (Note: these "links" are not nested, but they do belong_to :user.)
So, I'd like to just be able to ignore the creation of these links if possible. Like if a user submits 5 links, but 2 of them already exist for him, then I'd just like for those 2 to be ignored, while the other 3 are created. How should I go about doing this?
Edit: Thanks to Kandada, I'm now using this:
#links =
params[:links].values.collect.reject{
|link|
current_user.links.exists?(:url=>link[:url])}
#links = #links.collect{ |link|
current_user.links.create(link)
}.reject { |p| p.errors.empty? }
So I separated the two, to first check if there are any that exist, then to create those that weren't rejected. Is there a better way to do this, like maybe combining the two statements would increase performance? If not, I think I'm pretty satisfied. (thank you again Kandada and j.)

Try this:
#links = current_user.links.create(params[:links].reject{ |link|
current_user.links.exists?(:url=>link[:url]) })
Alternatively you can add an uniqueness check in the Link model for the url attribute.
class Link
validates_uniqueness_of :url, :scope => [:user_id]
end
In your controller:
#links = current_user.links.create(params[:links])
The result set returned is an array of newly created Link objects. Any links matching the existing links are ignored.
Edit
Here is another way to do this in one pass.
#links = params[:links].map{|link|
!current_user.links.exists?(:url=> link[:url]) and
current_user.links.create(link)}.select{|link| link and
link.errors.empty?}
I still think you should get your unique validation working and use this code afterwards:
#links = current_user.links.create(params[:links]).select{|link|
link.errors.empty?}
In the latter approach uniqueness validation is done in the model. This ensures link url uniqueness regardless how the link is created.

Reject the existent links before create them:
new_links = params[:links].reject{ |link| current_user.links.exists?(link) }
Somethink like this. Not sure about this code...

Related

Having two different sort of validate, from user or app itself in rails

My program is trying to create some groups automatically, with a prefix of 'automated_group', it won't show up when loading all groups, can't be edited, and some more stuff. But I have to limit users from doing it.
But if I make a validate function, it won't let my app do it and group.save returns false. Even when updating other attributes, it won't let me save it, cause the name won't validate.
Is there any other way? Sometimes use validation, or maybe check who's changing the value?
Thanks in advance
You can use a permission system like cancan (https://github.com/ryanb/cancan). Then you can define someting like this:
can :manage, Group, automated_group: false
I've found the half of the answer in skip certain validation method in Model
attr_accessor :automated_creation
validate :check_automated_name, unless: :automated_creation
def check_automated_name
#...
end
and in my controller:
def get_automated_group
name = "automated_group_#{product.id}"
group = Group.find(name: name).first
group Group.new(automated_creation: true) unless group.blank?
returrn group
end
When updating:
I'll check in check_automated_name function that it any change on name has happened with:
group.name_changed?
so any thing else can be updated except 'name', which the only way of creation is from rails itself.

Can I make Rails update_attributes with nested form find existing records and add to collections instead of creating new ones?

Scenario: I have a has_many association (Post has many Authors), and I have a nested Post form to accept attributes for Authors.
What I found is that when I call post.update_attributes(params[:post]) where params[:post] is a hash with post and all author attributes to add, there doesn't seem to be a way to ask Rails to only create Authors if certain criteria is met, e.g. the username for the Author already exists. What Rails would do is just failing and rollback update_attributes routine if username has uniqueness validation in the model. If not, then Rails would add a new record Author if one that does not have an id is in the hash.
Now my code for the update action in the Post controller becomes this:
def update
#post = Post.find(params[:id])
# custom code to work around by inspecting the author attributes
# and pre-inserting the association of existing authors into the testrun's author
# collection
params[:post][:authors_attributes].values.each do |author_attribute|
if author_attribute[:id].nil? and author_attribute[:username].present?
existing_author = Author.find_by_username(author_attribute[:username])
if existing_author.present?
author_attribute[:id] = existing_author.id
#testrun.authors << existing_author
end
end
end
if #post.update_attributes(params[:post])
flash[:success] = 'great!'
else
flash[:error] = 'Urgg!'
end
redirect_to ...
end
Are there better ways to handle this that I missed?
EDIT: Thanks for #Robd'Apice who lead me to look into overriding the default authors_attributes= function that accepts_nested_attributes_for inserts into the model on my behalf, I was able to come up with something that is better:
def authors_attributes=(authors_attributes)
authors_attributes.values.each do |author_attributes|
if author_attributes[:id].nil? and author_attributes[:username].present?
author = Radar.find_by_username(radar_attributes[:username])
if author.present?
author_attributes[:id] = author.id
self.authors << author
end
end
end
assign_nested_attributes_for_collection_association(:authors, authors_attributes, mass_assignment_options)
end
But I'm not completely satisfied with it, for one, I'm still mucking the attribute hashes from the caller directly which requires understanding of how the logic works for these hashes (:id set or not set, for instance), and two, I'm calling a function that is not trivial to fit here. It would be nice if there are ways to tell 'accepts_nested_attributes_for' to only create new record when certain condition is not met. The one-to-one association has a :update_only flag that does something similar but this is lacking for one-to-many relationship.
Are there better solutions out there?
This kind of logic probably belongs in your model, not your controller. I'd consider re-writing the author_attributes= method that is created by default for your association.
def authors_attributes=(authors_attributes)
authors_attributes.values.each do |author_attributes|
author_to_update = Author.find_by_id(author_attributes[:id]) || Author.find_by_username(author_attributes[:username]) || self.authors.build
author_to_update.update_attributes(author_attributes)
end
end
I haven't tested that code, but I think that should work.
EDIT: To retain the other functionality of accepts_nested_Attributes_for, you could use super:
def authors_attributes=(authors_attributes)
authors_attributes.each do |key, author_attributes|
authors_attributes[key][:id] = Author.find_by_username(author_attributes[:username]).id if author_attributes[:username] && !author_attributes[:username].present?
end
super(authors_attributes)
end
If that implementation with super doesn't work, you probably have two options: continue with the 'processing' of the attributes hash in the controller (but turn it into a private method of your controller to clean it up a bit), or continue with my first solution by adding in the functionality you've lost from :destroy => true and reject_if with your own code (which wouldn't be too hard to do). I'd probably go with the first option.
I'd suggest using a form object instead of trying to get accepts_nested_attributes to work. I find that form object are often much cleaner and much more flexible. Check out this railscast

Adding a parameter to an instance variable in Rails

I am creating a instance variable that gets passed to my view. This variable 'post' has a user_id associated with it and I wanted to add an extra attribute called 'username' so I can also pass that and use it in the view.
Here is an example of what I would like to do.
#post = Post.find(params[:id])
#post.username = User.find(#post.user_id).username
A username column does exist on my Users model but not my Songs model. So it won't let me use
#post.username
I know I can just make an entirely new instance variable and put that information in there but I would like to keep everything nice and neat, in one variable. Which will also make my json rendered code look cleaner.
Any ideas on how I can accomplish this?
Thanks!
Based on the presence of a user_id in your Post model, you probably already have an association set up that can retrieve the username. It will probably save a lot of trouble to simply use the existing association:
#post = Post.find(params[:id])
username = #post.user.username
If you're likely to be querying more than one post at a time (e.g., on an index page, calling .includes to tell Rails to eager-load an association will help you avoid the N+1 problem:
#posts = Post.includes(:user).all
Finally, to include the associated record in your JSON output, pass the :include parameter as you serialize:
# in controller
render :json => #post.to_json(:include => :user)
This question includes a much more comprehensive discussion of serialization options. Well worth a read.
No need to pass a separate instance variable.
1. You can use #post.user.username in view itself.
2. Or you can create a helper and pass #post.user
def username user
user.username
end

Rails gem rails3-jquery-autocomplete how to scope by user

I'm using the Rails gem rails3-jquery-autocomplete to add categories to posts.
I would like to restrict the search to include only categories that belong to the current user or post's author in the results.
The documentation says that I can specify a scope:
:scopes
Added option to use scopes. Pass scopes in an array. e.g :scopes =>
[:scope1, :scope2]
But I'm not sure how I would pass the user id here?
It seems like a comon scenario, am I missing something obvious?
I found an answer that suggests modifying the get_item method, but that seems to break the auto-complete
Scoping the results for rails3 jquery autocomplete plugin
In posts_controller:
def get_autocomplete_items(parameters)
items = super(parameters)
items = items.where(:user_id => current_user.id)
end
I'm first calling the original get_autocomplete_items method, and then filtering out the results by current_user.id.
This question helped:
Rails 3: alias_method_chain still used?
I had a similar problem I solved thanks to the answers above.
My autocomplete also worked against a User model, but I needed to restrict the results to the user's institution (Institution has many :users). My controller creates an #institution instance variable that is accessed in the view.
Although the get_autocomplete_items method cannot directly access the instance variable, I found that the data CAN be passed to autocomplete as a parameter (note: I use the simple_forms gem, so the input call looks a little different than the standard rails syntax).
In my view:
<%= f.input :email, :url => autocomplete_user_email_institutions_path(:institution_id=>#institution.id.to_s), :as => :autocomplete %>
In my controller:
autocomplete :user, :email, :extra_data => [:first_name, :last_name]
def get_autocomplete_items(parameters)
super(parameters).where(:institution_id => params[:institution_id])
end
My autocomplete list is now scoped to just the users who work for a particular institution.
deb's answer works for me.
The code can be cleaned up a bit:
def get_autocomplete_items(parameters)
super(parameters).where(:user_id => current_user.id)
end
There is small update to code for those who have having trouble with super method.because of dynamic dispatch it above code need to replaced as below:
def get_autocomplete_items(parameters)
items = super(parameters)
items = items.where(searchable: true)
end
to this:
def get_autocomplete_items(parameters)
items = active_record_get_autocomplete_items(parameters)
items = items.where(searchable: true)
end
Reference: https://github.com/crowdint/rails3-jquery-autocomplete/issues/278
To answer the question posed by #ctilley79, multiple autocompletes is not a problem because, in addition to the possibility of passing more values in the params hash, you also have access to the autocomplete parameters. On my form (as an example), I have both a City and a Zip autocomplete. I need to restrict the City to those in a certain state. So my controller action looks like this:
def get_autocomplete_items(parameters)
if (parameters[:model] == City)
super(parameters).where("state_id" => params[:state_id])
else
super(parameters)
end
end
You also have access to the method in case you need it. Do logger.debug on the parameters to see all that is available.
I know the gem and the question are old but I found myself using this gem and needing this answer recently... None of the old answers will work anymore because in the source code, the method get_autocomplete_items is generated dynamically and has the ORM prepended on the method name. This is what got it working for me. I assume most folks are using ActiveRecord too but check the autocomplete.rb method 'get_prefix' to figure out what you should prepend to the method name to get it working.
Hope this saves someone a bunch of time. Be the change you want to see and all that ;)
def active_record_get_autocomplete_items(parameters)
super(parameters).where(id: current_user.id)
end
I faced a similar problem. Our site is multi-tenant, so everything needs to be scoped to the tenant.
To make this easier, I modified rails3-jquery-autocomplete to accept another option called :base_scope. It takes a string, that gets eval'd instead of using the model. All the other functionality works, so you can append additional scopes and where clauses if you need to.
My fork is here: https://github.com/GiveCorps/rails3-jquery-autocomplete
I am not sure that the tests i wrote prove it will always work. I just checked that it was using the scope instead of the model in the items method.
i would appreciate any thoughts on it. Not sure whether it merits a pull request.

Rails best practice: conditional action, multiple actions, or method?

I'm curious to get some input on a chunk of code I've been working on recently.
I have a model, photos, which sometimes (but not always) belong_to a collection. I have a page for users to manage a collection, they can add any unassigned photos to the collection or remove photos from the collection.
This is an "edit multiple" situation, so I created two new controller actions: select, which handles the GET request and the view, and assign which handles the PUT request from the checkboxes in the select view.
Because the user can be either adding photos to a collection or removing photos from a collection, my assign action has a condition in it, and it looks like this:
def assign
#photos = Photo.find(params[:photo_ids])
case params[:assignment]
when 'add'
#photos.each do |p|
p.collection = #collection
p.save!
end
notice = "Photos added to collection."
when 'remove'
#photos.each do |p|
p.collection = nil
p.save!
end
notice = "Photos removed from collection."
end
redirect_to select_collection_photos_path(#collection), :notice => notice
end
This works exactly as expected. However, I feel uncomfortable with it, it doesn't seem to fit the "Rails Way."
Other Rails developers, when you have this kind of situation, would you handle it as I have? Would you split this across two controller actions (ie add_to_collection and remove_from_collection), or would you move it to the model? If you were to move it to the model, what would that look like?
I'd appreciate any suggestions and feedback. Thanks!
There's probably a few different ways you could refactor this, but the most obvious one seems to be moving all the photo logic to the Photo model. Even though this is your photos controller, it shouldn't know that much about the Photo model.
I would probably do something along these lines in your controller:
def assign
Photo.update_collection(params, #collection)
redirect_to select_collection_photos_path(#collection), :notice => "Photo collection updated"
end
Then in your Photo model:
class Photo < ActiveRecord::Base
def self.update_collection(params, collection)
photos = Photo.find(params[:photo_ids])
case params[:assignment]
when 'add'
photos.each {|p| p.add_collection(collection) }
when 'remove'
photos.each {|p| p.remove_collection }
end
end
def add_collection(collection)
self.collection = collection
save!
end
def remove_collection
self.collection = nil
save!
end
end
Breaking the functionality up into smaller model methods makes it easier for unit testing, which you should be doing if you're not :)
This is actually a prime candidate for accepts_nested_attributes_for.
Instead of thinking about new actions in the controller, stick to the standard REST conventions whenever possible. Excepting fancy UI display stuff (like your select action), very rarely do I find that I need to deviate from the standard CRUD actions present in a generated scaffold_controller.
If you set accepts_nested_attributes_for :collection in your Photo model, you should be able to build up a special form that assigns collections to photos. I won't go into the full details here, but will instead point you to http://railscasts.com/episodes/196-nested-model-form-part-1 and http://railscasts.com/episodes/197-nested-model-form-part-2 . It'll be more work in the view, but you'll come out far ahead in more simple, easily testable controllers and models.

Resources