In the show action, there is logic that requires me to define:
#object.nested_object.new (or #object.nested_object.build or #object.nested_object.create)
However, when it's time to show a list of the nested_objects using something like:
#object.nested_objects.each do |nested_objects|
#display
end
There is an extra line item for the temporary nested object created with #object.nested_object.new.
Is there a way to forcefully remove that temporary object before I display the list of the actual nested_objects?
Or is there another way of accomplishing the following:
-creating a temporary nested_object for logic tests
-showing a list of nested_objects
I've tried stuff like:
temp_nested_object = #object.nested_object.new
temp_nested_object.delete
but wasn't successful.
Thanks for your time!
UPDATE:
I'm trying to accomplish this:
<% if can? :create, #project.tasks.build %>
From here:
https://github.com/ryanb/cancan/wiki/Nested-Resources
I've tried to find other ways of accomplishing this:
https://github.com/ryanb/cancan/issues/608
but seems like I need to workaround it in the view.
First you should not create a temporary object.
if you follow the previous advice, simply do:
#object.nested_objects.select(&:persisted?).each do |nested_objects|
#display
end
This will filter objects from your db (no need to delete a temporary object, it will disappear at the end of the request).
One possibility is to just remove it from the collection with:
# Remove last object in collection
#object.nested_objects.pop
Related
In my rails app I have a collectionproxy that is an array (I think) of records. I want to take a record from the middle of the array and put it at the beginning. I don't know the position of the item, but I can find it using an attribute on user model. It seems like some methods aren't available to use on a collectionproxy.
I tried:
user_images = user.images
user_images.insert(0, user_images.delete(user.images.find_by_id(user.primary_image_id))
but got an error that I gave 2 arguments but it expected 1. I'm guessing because the insert method that is used on arrays isnt the same method that is used on collectionproxies. What's the best way to do this?
Edit: I just need this to display the items in the view, I don't need to change at the database level.
As very few methods are available for collection proxy, first change the collection proxy to array and then manipulate it.
Here is the code to do so,
user_images = user.images.to_a //converted collection to array
user_images.unshift(user_images.detect{ |image| image.id == user.primary_image_id}).uniq //used unshift
puts user_images
The magic we have done here is detect the images that's the primary image of the user and unshift into array
The unshift adds the object in the beginning.
Now remove the duplicated oject which is already there at someplace by using uniq.
That's it your required objects comes first into the array and you can use this in the view as active record collection is used.
It looks like there's currently no way to add to the beginning of a CollectionProxy. The prepend and sort methods were removed. Here's the API for the deprecated prepend method:
http://apidock.com/rails/v4.2.1/ActiveRecord/Associations/CollectionProxy/prepend
You could re-think this slightly, and use the append or << operator along with delete to copy elements to the end of the collection, and delete them from the middle. It's not ideal, but it might be a workaround until you have a better solution.
How can i remove the last element from an ActiveRecord_Relation in rails?
e.g. if I set:
#drivers = Driver.all
I can add a another Driver object called #new_driver to #drivers by doing:
#drivers << #new_driver
But how can I remove an object from #drivers?
The delete method doesn't seem to work, i.e.
#drivers.delete(0)
You can use the reject! method, this will remove the object from the collection without affecting the db
for example:
driver_to_delete = #driver.first # you need the object that you want removed
#drivers.reject!{|driver| driver == driver_to_delete}
Very late too, but I arrived here looking for a fast answer and finished by thinking by myself ;)
Just to clarify about the different answers and the Rails 6.1 comment on accepted answer:
The OP wanted to remove one entry from a query, but NOT remove it from database, so any answer with delete or destroy is just wrong (this WILL delete data from your database !!).
In Ruby (and therefore Rails) convention, shebang methods (ending with !) tend to alter the given parameter. So reject! would imply modifying the source list ... but an ActiveRecord_Relation is basically just a query, NOT an array of entries !
So you'd have 2 options:
Write your query differently to specifically say you don't want some id:
#drivers.where.not(id: #driver_to_remove) # This still is an ActiveRecord_Relation
Use reject (NO shebang) on your query to transform it into an Array and "manually" remove the entry you don't want:
#drivers.reject{ |driver| driver == #driver_to_remove}
# The `reject` forces the execution of the query in DB and returns an Array)
On a performance point of view, I would personally recommend the first solution as it would be just a little more complex against the DB where the latter implies looping on the whole (eventually large) array.
Late to the question, but just had the same issue and hope this helps someone else.
reject!did not work for ActiveRecord_Relation in Rails 4.2
drop(1) was the solution
In this case #drivers.drop(0) would work to drop the first element of the relation
Since its an array of objects, have you tried to write something like #drivers.delete(#new_driver) or #drivers.delete(id: #new_driver.id) ?
This is the documentation you need:
#group.avatars << Avatar.new
#group.avatars.delete(#group.avatars.last)
--
.destroy
The problem you've got is you're trying to use collection methods on a non-collection object. You'll need to use the .destroy ActiveRecord method to get rid of the record from the database (and consequently the collection):
#drivers = Driver.all
#drivers.last.destroy
--
Scope
.delete will remove the record from the DB
If you want to pull specific elements from the db to populate the #drivers object, you'll need to use a scope:
#app/models/driver.rb
Class Driver < ActiveRecord::Base
scope :your_scope, -> { where column: "value" }
end
This will allow you to call:
#app/controllers/drivers_controller.rb
def index
#drivers = Driver.your_scope
end
I think you're getting the MVC programming pattern confused - data manipulation is meant to happen in the model, not the controller
As stated above, reject! doesn't work in Rails 4.2, but delete does, so #drivers.delete(#new_driver) works, and more generally:
#drivers.delete(Driver.where(your condition))
I call first_or_create like so:
collection = Collection.first_or_create(:title => title)
Is there a way to determine if the result is an existing entry or a freshly created one? So far the best solution I've come up with is to use first_or_initialize:
collection = Collection.first_or_initialize(:title => title)
if collection.id.nil?
<process>
collection.save
end
But this feels a bit hacky. Is there a way to get this information directly from first_or_create?
first_or_create takes a block that'll only be executed if a new record is created, you can set some flag inside that block to indicate it's a new record, for example
MyObject.where(attr: "value").first_or_create do |obj|
#my_object_created = true
end
As far as I know you can't know. Two options are to check the created_at time (unreliable), or instead use first_or_initialize, then check to see if new_record? is true, and if so, do your other operations and then call save!. This may be the best approach for you anyway, since you may very well not want to finalize the save until the other relations are saved, and you probably want to do all of that in the same database transaction.
Using first_or_create you can't know for sure is it a newly created object or one from the database. Possible tricky solution is to compare created_at value with current time. This works if you don't create objects often.
Btw, why you need to know is it the newly created object or not?
In my Rails app, there are nodes which can point to each other by filling in the id of their previous_node_id and next_node_id columns. When I create a node, it successfully points back to the previous node, but the previous node does not change and its next_node_id value remains nil. How do I fix this? (I'll also appreciate any comments about how to improve the code.)
NodesController:
def create
#node = current_user.nodes.build(params[:node])
if params[:previous_node_id].present?
setup_node_pointers(Node.find(params[:previous_node_id]), #node )
end
if #node.save
...//end of the controller
Relevant code from the Helper:
def setup_node_pointers(previous_node, current_node)
previous_node.next_node_id = current_node.id
current_node.previous_node_id = previous_node.id
previous_node.save
end
This is because current_node.id is nil. The build command doesn't save the record to the db so there's no id assigned to #node. Save #node before your call to the helper and it should work. If you need to bypass validations to save it, do #node.save(validate: false)
Its surprising how many errors there were in so little code!
Need to save current node 1st (see Steve's answer)
:previous_node_id was part of the node params, so it should have been
params[:node][:previous_node_id]
cannot call helper from controller. the current_node was setup in the initial build, so
some of the helper code was redundant, causing confusion.
I'm sure this has been asked already, but I can't find the answer.
I have a Project model, which has a belongs_to relationship with my Client model. A client has a name, but a project doesn't necessarily have a client.
In my view, I've got code like this:
<%=h project.client && project.client.name %>
because if the project doesn't have a client then trying to access project.client.name causes a NoMethodError (nil doesn't have a method called name).
The question is, is it acceptable to have this kind of nil checking in the view, or should I be looking for another way around it?
Just use
project.client.try(:name)
I think its perfectly acceptable - this is view logic, you are more or less deciding whether or not to show portions of your view, based on whether there is data.
I run into this all the time, and yes it's annoying. Even when there is supposed to never be a nil, dirty data that I inherited sometimes triggers it.
Your solution is one way of handling it. You could also add a method to Project called client_name that displays the client name if it exists, but then you are linking the models together more than some people recommend.
def client_name
client && client.name
end
You could also make a helper method to do it, but you can end up writing a lot of them. :)
As mentioned by Skilldrick below, this is also useful to add a default string:
def client_name
client ? client.name : "no client"
end
You can use delegate in your Project class, so this way you will respect the Law of demeter which says that you should "talk only to your immediate friends".
project.rb
class Project
delegate :name, to: :client, prefix: true, allow_nil: true
end
So this way the project object will know where to ask about the client's name:
#You can now call
project.client_name
See more about delegate in the Rails documentation.
my hacky solution is to yield a block and rescue the error. Many would say using rescue as logic is very bad form. Just don't use this where you would actually need to know when something is nil and shouldn't be.
In application_helper.rb:
def none_on_fail
begin
return yield
rescue
return "(none entered)"
end
end
Then in the view:
<%= none_on_fail { project.client.name } %>
Then methods can be chained as deep as needed and it can be used on any method BUT it will cover up other potential problems with models/relationships/methods if they exist. I would equate it to taking out a splinter with a flamethrower. Very effective with painful consequences if used improperly.
I think these checks can usually be eliminated with a bit of thought. This has the benefit of keeping your view code cleaner, and more importantly, keeping logic out of the view layer, which is a best practice. Some templating engines don't allow any logic in the view.
There are at least a couple of scenarios. Let's say you have a show action that depends on an instance variable. I'd say if the record is not found the controller should not render the html, by redirecting or something else. If you have a loop in the view for an array, use #array.each do |a| end so that it doesn't evaluate if the array is empty. If you truly want an application default in the view, try loading it from a config file, e.g. #page_title || #{#APP_CONFIG['page_title']} (see Railscasts #85). Remember you may want to change these strings later, for example translating the UI.
Those are a couple scenarios where presence checks and usage of try can be avoided. I'd try to avoid them if possible. If you can't avoid them, I'd put the conditional checks in a view helper and add a helper unit test for it to verify (and document) both code paths.