Ruby/Rails. Passing variable into parameters - ruby-on-rails

I have a lot pages in my app. And for every page I need to create a new variable and describe method. And if I will need to change something, I will have to change on every page. So the idea is to create universal method in application_controller.rb.
For example, I have a lot of categories in my Posts and to target some category for page I did: #posts_for_interesting = Post.where(interesting: true), other category, for example: #posts_for_photos = Post.where(photos: true).
And the application_controller.rb, have to look something like that:
def posts_for_all_pages(category)
#posts = Posts.where(category: true)
end
And, for example, photos_controller.rb must look like that:
posts_for_all_pages(photos)
And how can I pass this photos to Post.where(category: true)?

Right here in your original code:
def posts_for_all_pages(category)
#posts = Posts.where(category: true)
end
The category in Posts.where(category: true) will not a variable, it will be a hard coded symbol :category, so it won't work. Instead, write this:
def posts_for_all_pages(category)
#posts = Posts.where(category => true)
end
a small change, but definitely has a different meaning.
Then when you call the method, you pass a symbol to it:
posts_for_all_pages(:photos)

Related

How to modify a parameter before it is sent to the database

I want to modify one of the params in the update function before it gets updated.
I can extract the value doing: abort house_show_params[:pricing_option].inspect but I would like to modify it.
I've tried:
house_show_params[:pricing_option] = 5
house_show_params.update_attribute(pricing_option: 5)
And that did not work.
Update Function
def update
#house_show = current_user.house_show
respond_to do |format|
if #house_show.update(house_show_params)
......
I would do that in the model (the controller is not the proper place) using the write_attribute method.
For example, if I want my company_name field to be stripped before being stored, I would do (in my model):
def company_name=(value)
self[:company_name] = value&.strip
end
This would be the same as doing (you can choose whatever way you prefer):
def company_name=(value)
my_model.write_attribute(:company_name, value&.strip)
end
This page illustrates the options you have, although you should have your question answered with the two examples above:
https://davidverhasselt.com/set-attributes-in-activerecord/

Indexing method that gets parameters in Rails 4

I have a store application with a Product scaffold and I want to enable categories and pages that show each category of products.
My product model has a "category" attribute and I use the link_to helper to create links to each category.
In my products controller I added a method called index_by_category(cat):
def index_by_category(cat)
#products_by_category = Product.where(category: cat)
end
I'm trying to iterate #products_by_category in a view I created with the corresponding name (product/index_by_category.html.erb) just like the regular index method do. For some reason it render me the regular index method of products which shows ALL of them, even though the URL is:
http://localhost:3000/products?index_by_category=Food
This is what I did in my route.rb file:
get 'products/index_by_category'
I'm newbie to Rails development so if I did something which is wrong from the roots and the rails approach to the problem should be entirely different I also be happy to know for the sake of learning.
You are doing things a bit wrong. Try to write your controller like this:
def index_by_category
#products_by_category = Product.where(category: params[:category])
end
And update your route
get 'products/category/:category', to: 'products#index_by_category
Then visit
http://localhost:3000/products/category/Food
UPDATE
if you really want to use index method for both cases you could do that by modifying it to something like this
def index
if params[:category]
#products = Product.where(category: params[:category])
else
#products = Product.all
end
end
and then just visit
http://localhost:3000/products?category=Food

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.

How to make an object visible in scope of entire controller?

So i need to get instantiate an object which requires parameters. I also need this object to be available in the scope of the entire controller once instantiated. How can this be done?
Edit: some code to help illustrate
def beginLoad(user, category)
#stuff
#gaobj = GraphAssistant.new(#arrays.fetch(0), #arrays.fetch(1))
values = #gaobj.externalize
ret = {"axis_label" => values.fetch(0), "out" => values.fetch(1), "i" => values.fetch(2)}
end
But when I try to call it again from this method:
def resumeLoad(direction)
if direction.eql? "left"
#gaobj.decrementPosition
elsif direction.eql? "right"
#gaobj.incrementPosition
end
#other stuff
end
it doesnt work. I suppose I could do all of this logic in the view, what implications will that have on performance though?
Prefix it with an #sign: #foo = Foo.new.
Update: Sounds like you need this persisted to the session.
At the end of beginLoad, add:
session[:foo] = #foo
At the beginning of resumeLoad, add:
#foo = session[:foo]
If both of those functions are being called from the same controller action, the instance variable (#gaobj) should be accessible to both. You will need to make sure that #beginLoad is called before #resumeLoad for every incoming request. Is that the problem?

How can I pass objects from one controller to another in rails?

I have been trying to get my head around render_to but I haven't had much success.
Essentially I have controller methods:
def first
#I want to get the value of VAR1 here
end
def second
VAR1 = ["Hello", "Goodbye"]
render_to ??
end
What I can't figure out is how to accomplish that. Originally I just wanted to render the first.html.erb file but that didn't seem to work either.
Thanks
Edit: I appreciate the answers I have received, however all of them tend to avoid using the render method or redirect_to. Is it basically the case then that a you cannot pass variables from controller to controller? I have to think that there is some way but I can't seem to find it.
It is not a good idea to assign the object to a constant. True this is in a global space, but it is global for everyone so any other user going to this request will get this object. There are a few solutions to this.
I am assuming you have a multi-step form you are going through. In that case you can pass the set attributes as hidden fields.
<%= f.hidden_field :name %>
If there are a lot of fields this can be tedious so you may want to loop through the params[...] hash or column_names method to determine which attributes to pass.
Alternatively you can store attributes in the session.
def first
#item = Item.new(params[:item])
session[:item_attributes] = #item.attributes
end
def second
#item = Item.new(session[:item_attributes])
#item.attributes = params[:item]
end
Thirdly, as Paul Keeble mentioned you can save the model to the database but mark it as incomplete. You may want to use a state machine for this.
Finally, you may want to take a look at the Acts As Wizard plugin.
I usually don't have my controllers calling each other's actions. If you have an identifier that starts with a capital letter, in Ruby that is a constant. If you want to an instance level variable, have it start with #.
#var1 = ["Hello", "Goodbye"]
Can you explain what your goal is?
Have you considered using the flash hash? A lot of people use it solely for error messages and the like, it's explicitly for the sort of transient data passing you might be interested in.
Basically, the flash method returns a hash. Any value you assign to a key in the hash will be available to the next action, but then it's gone. So:
def first
flash[:var] = ["hello", "goodbye"]
redirect_to :action => :second
end
def second
#hello = flash[:var].first
end
way 1
Global variable
(fail during concurrent requests)
way 2
class variable
(fail during concurrent requests)
way 3
Stash the object on the server between requests. The typical way is to save it in the session, since it automatically serializes/deserializes the object for you.
Serialize the object and include it in the form somewhere, and
deserialize it from the parameters in the next request. so you can store attributes in the session.
def first
#item = Item.new(params[:item])
session[:item_attributes] = #item.attributes
end
def second
#item = Item.new(session[:item_attributes])
#item.attributes = params[:item]
end
way 4
The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed to the very next action and then cleared out.
def new
#test_suite_run = TestSuiteRun.new
#tests = Test.find(:all, :conditions => { :test_suite_id => params[:number] })
flash[:someval] = params[:number]
end
def create
#test_suite_run = TestSuiteRun.new(params[:test_suite_run])
#tests = Test.find(:all, :conditions => { :test_suite_id => flash[:someval] })
end
way 5
you can use rails cache.
Rails.cache.write("list",[1,2,3])
Rails.cache.read("list")
But what happens when different sessions have different values?
Unless you ensure the uniqueness of the list name across the session this solution will fail during concurrent requests
way 6
In one action store the value in db table based on the session id and other action can retrieve it from db based on session id.
way 7
class BarsController < UsersController
before_filter :init_foo_list
def method1
render :method2
end
def method2
#foo_list.each do | item|
# do something
end
end
def init_foo_list
#foo_list ||= ['Money', 'Animals', 'Ummagumma']
end
end
way 8
From action sent to view and again from view sent to other actions in controller.

Resources