Adding a parameter to an instance variable in Rails - ruby-on-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

Related

change params value before patch

I have a texfield in a form where a foreign key's value is displayed. now I want to update the value and save it to the DB. Here is the code:
for the form I use:
f.text_field :port, :value =>#entry.port.number, class:"form-
control", placeholder:"Port"
in the controller I am using a param method:
def entry_params
params.require(:entry).permit(:description,:rule_id, :protocol_id,
:url, :port)
end
the update method looks like this:
def update
#entry.url.name = params[:entry][:url]
#entry.port.number = params[:entry][:port]
if #entry.update(entry_params)
flash[:success] = "Entry was successfully updated!"
redirect_to entry_path(#entry)
else
render 'edit'
end
end
but if I want try to save it, it shows this error:
Url(#70247237379440) expected, got "www.drive.google.com" which is an
instance of String(#70247218839280)
Now my question is, (I'm relativ new to rails) how can I fix this? I know that it expect an object as a parameter but if I change the param like this:
params[:url] = #entry.url
it doesn't work.
There are two approaches I can think of here, depending on exactly what you're looking to achieve.
If you're looking to assign an entry a new url based on a string params, you can use something like the following:
#entry.url = Url.find_by_name(params[:entry][:url])
Depending on your models' setup, if there's a url_id column on your entries, you might be better off using a select field on this, passing the URL's name and id to the options. If you can add this info to your question, I can update / rule this out as needed.
If you're just looking to update the URL via the entry's form, you'd be best looking at using accepts_nested_attributes_for.
Doing this, you can directly update the associated objects through the parent's form. If this sounds like the right approach for you, let me know and I can provide more detail :)
Edit: as per your comment, is sounds like this is the option you're after. So, you'd need something like the following:
entry.rb
accepts_nested_attributes_for :url
In the form:
...
f.nested_fields_for :url do |url_fields|
url_fields.text_field :name
end
...
And you'll need to update the params in your controller to accept these nested fields. I can't remember the exact for they take, but it's something like:
def new / edit # whichever you're in
...
#entry.build_url unless #entry.url.present?
end
def entry_params
params.require(:entry).permit(:description,:rule_id, :protocol_id,
:port, url_attributes: [:name])
end
(It might be you need an empty array or a hash for url_attributes.)
That'll then directly update the associated url. FYI if you've not got an associated URL, you'll need to build one using #entry.build_url in the controller.
Hope this helps - if you've any questions / details to help clarify, let me know and I'll update as needed.

Why does Rails use the params variable rather than passing arguments through the function?

As title says, why does Rails prefer to use the #params variable inside of a Controller action when you are responding to the action instead of passing the individual parameters through the function arguments when we call the function?
Other frameworks use this (i.e, ASP MVC) and I was just wondering if there was a reason for that design decision, because it doesn't seem very intuitive.
Ie. Why does Rails do
def index
name = params[:name]
end
Instead of
def index(name)
end
The point is, most of the actions in a controller handles the view REST-fully. The params comes from the user's browser when they interact with the page or send a new variable request to the page.
These requests are variable, and Rails makes it uniform by maintaining the parameters in params hash. If the following GET requests arrive:
http://localhost:3000/products?color=red&quality=best
the params hash will automatically be populated as {'color' => 'red', 'quality' => 'best'}. Rails doesn't expect your action to manually handle the parameters.
Similarly, consider you are getting a POST request from a page where a user filled a form. In that scenario, the params obtain the parameters which are composed with form helpers inside views.
Though in hyptothetical case you are dealing with general methods instead of actions, such as below, you will have to do it by passing arguments.
def show
if params['color'] == 'red'
#product = obtain_product('red')
else
#,..
end
end
def obtain_product(color)
Product.where('color = ?', color).first
end
Hope it is clear. :)
#kidorrails has a great answer, and I wanted to add to it:
If you wanted to pass the params to each method directly, it would go against the #1 Rails convention - keep it DRY. By having a separate params hash, you not only have access to all the params you want, but you can access them through as many methods as you need
For example, take strong_params:
#controller
def new
#model = Model.new
end
def create
#model = Model.new(strong_params)
#model.save
end
private
def strong_params
params.require(:model).permit(:your, :params)
end
As #apneadiving mentioned, the params hash is created in another part of the stack, meaning it's available over all the methods required. It's most efficient & versatile way to do it IMO

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

Rails model create function

I used the nested model gem to create a Picture that can take tags. Now I have added an attribute to my model Picture so it has an attribute taglist. When I create a new tag, I want this to happen
class TagsController < ApplicationController
def create
#tag = Tag.new(params[:id])
if #tag.save
taglist = picture.taglist
taglist+=#tag.tagcontent
#tag.picture.update_attributes(:taglist => taglist)
end
end
end
and in my routes
resources :pictures do
resources :tags
end
When i make a new tag, nothing happens in the taglist attribute, like nothing happened, why?
It's hard to help due to lack of information, but I see two possible issues:
Tag.new(params[:id]) doesn't make sense. Assuming Tag inherits from ActiveRecord::Base, you need to pass it a hash of attributes (e.g. Tag.new(:name => 'mytag')) You are likely not getting into the if #tag.save block at all due to validation errors. Also, you don't need to provide an id to an object you want to create. The database chooses the id.
Inside the block, picture is undefined on the first line.
Why not try debugging with something like:
if #tag.save
taglist = picture.taglist
taglist+=#tag.tagcontent
#tag.picture.update_attributes(:taglist => taglist)
else
p "ERRORS:"
p #tag.errors.full_messages
end
See what errors that prints out into your console.
I definitely think that picture is probably undefined in the create method of the controller. Can you show us the view, the form you're using to create a new tag? Is there a form field through which you're choosing which photo gets the tag?
Please show us the association and your view for creating the new tag.
Actually, what I'd really recommend instead of cooking up your own is to use:
Agile Web Development's acts_as_taggable_on_steroids
It's an excellent plugin to make tagging easy; it has quite a few nifty features built in, including the searches, tag clouds, etc. We use it on our projects.

Constructing a Rails ActiveRecord where clause

What's the best way to construct a where clause using Rails ActiveRecord? For instance, let's say I have a controller action that returns a list of blog posts:
def index
#posts = Post.all
end
Now, let's say I want to be able to pass in a url parameter so that this controller action only returns posts by a specific author:
def index
author_id = params[:author_id]
if author_id.nil?
#posts = Post.all
else
#posts = Post.where("author = ?", author_id)
end
end
This doesn't feel very DRY to me. If I were to add ordering or pagination or worse yet, more optional URL query string params to filter by, this controller action would get very complicated.
How about:
def index
author_id = params[:author_id]
#posts = Post.scoped
#post = #post.where(:author_id => author_id) if author_id.present?
#post = #post.where(:some_other_condition => some_other_value) if some_other_value.present?
end
Post.scoped is essentially a lazy loaded equivalent to Post.all (since Post.all returns an array
immediately, while Post.scoped just returns a relation object). This query won't be executed until
you actually try to iterate over it in the view (by calling .each).
Mmmh, the best approach you want to use can be to spread this in 2 actions
def index
#post = Post.all
end
def get
#post = Post.where("author=?", params[:author_id])
end
IMHO it has more sense if you think about a RESTful API, index means to list all and get (or show) to fetch the requested one and show it!
This question is pretty old but it still comes up high in google in 2019, and also some earlier answers have been deprecated, so I thought I would share a possible solution.
In the model introduce some scopes with a test for the existence of the parameter passed:
class Post
scope :where_author_ids, ->(ids){ where(author_id: ids.split(‘,’)) if ids }
scope :where_topic_ids, ->(ids){ where(topic_id: ids.split(‘,’)) if ids }
Then in the controller you can just put as many filters in as you wish e.g:
def list
#posts = Post.where_author_ids(params[:author_ids])
.where_topic_ids(params[:topic_ids])
.where_other_condition_ids(params[:other_condition_ids])
.order(:created_at)
The parameter can then be a single value or a comma separated list of values, both work fine.
If a param doesn’t exist it simply skips that where clause and doesn’t filter for that particular criteria. If the param exists but its value is an empty string then it will ‘filter out’ everything.
This solution won’t suit every circumstance of course. If you have a view page with several filters on, but upon first opening you want to show all your data instead of no data until you press a ‘submit’ button or similar (as this controller would) then you will have to tweak it slightly.
I’ve had a go at SQL injecting this and rails seems to do a good job of keeping everything secure as far as I can see.
You should model url using nested resources. The expected url would be /authors/1/posts. Think of authors as resources. Read about nested resources in this guide: http://guides.rubyonrails.org/routing.html (scroll to 2.7 - Nested Resources).
Would something like this work?
def get
raise "Bad parameters...why are you doing this?" unless params[:filter].is_a?(Hash)
#post = Post.where(params[:filter])
end
Then you can do something like:
?filter[author_id]=1&filter[post_date]=... etc.

Resources