How would I change a scope on a chain dynamically based on a parameter in the URI?
For example:
http://www.example.com/object?status=approved
would lead to the ObjectController#index:
def index
#objects = Object.approved.<other-chains>
end
If status was pending, the controller method would be something like:
#objects = Object.pending.<other-chains>
You wouldn't. You'd define two scopes, pending and approved, and invoke them conditionally based on the URL. You can't (or at the very least, shouldn't) change your scopes dynamically at run time, or you're going to break subsequent requests terribly.
If you want to avoid branching if/elses, you can just send the method to your model, after making sure that it is in a pre-approved list of acceptable methods:
class MyController
def index
#objects = Object.send(scope).chain.chain.chain
end
protected
# return "pending", "approved", or "scoped",
# so that Object.send(scope) *always* works, and returns a chainable relation
def scope
scopes = %w(pending approved)
scopes.include?(params[:status].to_s) ? params[:status] : "scoped"
end
end
By defaulting to "scoped", you ensure that the method invoked will return a relation onto which additional methods can be chained.
How about:
#objects = Object.send(params[:status])
Related
I am using this function to find users, which i am using .require only worked when i sent both or at least one parameter but if i send empty i got errors, It should not be mandatory to send parameters
def find_params
params.require(:person).permit(:name, :age)
end
if i send the name or age i will work, but if i send nothing i am getting this error
params is missing
if i send empty like this:
{} or null it should work correctly returning all the users
or should not i use this to search users?
params.require(:person).permit(:name, :age)
might i have to user like this?
params[:name] and params[:age]
i am working with reactjs
i am sending the payload liket this:
{name:"ed", age:"12", skin:"black", weight: "180lbs", height:"183"}
Rails' StrongParameters were built for a very specific use:
It provides an interface for protecting attributes from end-user assignment. This makes Action Controller parameters forbidden to be used in Active Model mass assignment until they have been explicitly enumerated.
That means when you use the params just to read from the database, then there is no need (and hardly any advantage) to use StrongParameters.
Instead, I would just use the params directly in the controller like this:
def index
#users = User
.filter_by(:name, params.dig(:person, :name))
.filter_by(:age, params.dig(:person, :age))
# ...
end
And to make this work you will need to define an filter_by scope in your app/models/user.rb:
scope :filter_by, -> (attr, value) { where(attr => value) if value.present? }
The whole point of using ActionController::Parameters#require is to cause your create/update method to bail early if the parameter you expect to be a hash isn't sent at all since there is no point in proessing the request further and this prevents a potential uncaught nil error.
If you want to allow a key to be null use #fetch instead:
params.fetch(:person, {})
.permit(:name, :age)
#fetch allows you to pass a second key which is the default value and it returns a new ActionController::Parameters instance.
But it looks like you're actually sending flat parameters which are not nested in which case you don't need fetch either:
params.permit(:name, :age)
Note that Rails by default has parameters wrapping turned on for JSON requests and both will very likely work.
You can use Find User
Routes.rb
get "search_user", to: "users#search_user"
controllers/users_controller.rb
def search_user
#users = User.search(params[:name], params[:age], params[:skin], params[:weight], params[:height]) // search name or age
// you can use byebug to check #users
end
models/user.rb
def self.search(name, age, skin, weight, height)
if name.blank? & age.blank? & skin.blank? & weight.blank? & hight.blank?
all
else
where('name LIKE ? OR age LIKE ? OR skin LIKE ? OR weight LIKE ? OR height LIKE ?', "%#{name}%", "%#{age}%", "%#{skin}%", "%#{weight}%", "%#{height}%")
end
end
=> This is my way which i used. Hope to help you.
Is it possible to return the parent object of a given attribute?
Example
a = User.birthdate
a.parent_object ... should return the user record that is the parent of the birthdate attribute
A better example?
Helper
def item_grade(subject, obj)
obj.scale.grades.find(subject.grade_id).name # would return something like "Pass", "Fail", "Good Job"
end
In the view
item_grade(#course.subject, #course)
This approach requires two options to be passed to the helper. It seems I should be able to pass #course.subject and then get the parent object from that
Helper
def item_grade(subject)
a = subject.parent_object.scale
a.grades.find(subject.grade_id).name
end
View
item_grade(#course.subject)
This approach requires two options to be passed to the helper.
You can remove some duplication by doing this, for example.
def item_grade(obj, property)
obj.scale.grades.find(obj.send(property).grade_id).name
end
item_grade(#course, :subject)
Now you don't have to repeat #course in the call.
Having to pass two parameters is much less harmful than any sort of hackery you can come up with (thanks #muistooshort). There's no built-in way to do this.
I have an api. In that api is a basecontroller that all other controllers inherit from. The basecontroller handles authentication and whether or not the API is on at all, etc.
There are users, and users can belong to a group. Users table has a group_id column.
I'm trying to introduce a new feature, whereby a select on the settings page for admin controls which users are shown from what groups. If an option is selected, only users from that group should be shown by the api.
I could go into each controller (there is one controller for each of a few different tasks - getting all users info, just active_users ids, a single users information, etc) and add the extra statement to each
if !settings.api_group.nil?
#add the additional where("group_id = ?, settings.group_id)
but that seems like a lot of repeating myself (doing it in 8 different places)
Is there some way to add something to the basecontroller that says:
if this setting option is not nil, only return user information if they are in this group
?
Thanks
You can add a method to the BaseController, and call it in each action that should have this restriction. Something like this:
in base_controller.rb:
protected
def filtered_users
if settings.api_group
User.where(:group_id => settings.group_id)
else
User.scoped
end
end
and in the controllers that inherit from it:
def index
#users = filtered_users
end
This way, you only define the filtering in one place. If it needs to change later, you only have to change it in one place. Because filtered_users actually returns a Relation, you can continue to alter the query by tacking additional .where clauses, etc, like this:
#users = filtered_users.joins(:posts).where('posts.created_at > ?', 1.week.ago)
FYI my answer was exactly what I thought it might have to be in the initial post. I'd love for there to be a more DRY solution, but I ended up doing something like this:
IN USER MODEL
def find_in_api_group
# NOTE settings.api_group is a string => "1,2,4"
if settings.api_group.nil? || settings.api_group.blank?
where("") # THERE HAS TO BE BETTER WAY OF SAYING THIS WITHOUT INTERRUPTING THE CHAIN
else
where("group_id IN (?)", settings.api_group)
end
end
IN VARIOUS CONTROLLERS
user = User.find_in_api_group
#then chain various error tests and additional activeRecord statement
I have a method like this that goes through an array to find different APIs and launch a delayed_job instance for every API found like this.
def refresh_users_list
apis_array.each do |api|
api.myclass.new.delay.get_and_create_or_update_users
end
end
I have an after_filter on users#index controller to trigger this method. This is creating many jobs to be triggered that will eventually cause too many connections problems on Heroku.
I'm wondering if there's a way I can check for the presence of a Job in the database by each of the API that the array iterates. This would be very helpful so I can only trigger a particular refresh if that api wasn't updated on a given time.
Any idea how to do this?
In config/application.rb, add the following
config.autoload_paths += Dir["#{config.root}/app/jobs/**/"]
Create a new directory at app/jobs/.
Create a file at app/jobs/api_job.rb that looks like
class ApiJob < Struct.new(:attr1, :attr2, :attr3)
attr_accessor :token
def initialize(*attrs)
self.token = self.class.token(attr1, attr2, attr3)
end
def display_name
self.class.token(attr1, attr2, attr3)
end
#
# Class methods
#
def self.token(attr1, attr2, attr3)
[name.parameterize, attr1.id, attr2.id, attr3.id].join("/")
end
def self.find_by_token(token)
Delayed::Job.where("handler like ?", "%token: #{token}%")
end
end
Note: You will replace attr1, attr2, and attr3 with whatever number of attributes you need (if any) to pass to the ApiJob to perform the queued task. More on how to call this in a moment
For each of your API's that you queue some get_and_create_or_update_users method for you'll create another Job. For example, if I have some Facebook api model, I might have a class at app/jobs/facebook_api_job.rb that looks like
class FacebookApiJob < ApiJob
def perform
FacebookApi.new.get_and_create_or_update_users(attr1, attr2, attr3)
end
end
Note: In your Question you did not pass any attributes to get_and_create_or_update_users. I am just showing you where you would do this if you need the job to have attributes passed to it.
Finally, wherever your refresh_users_list is defined, define something like this job_exists? method
def job_exists?(tokens)
tokens = [tokens] if !tokens.is_a?(Array) # allows a String or Array of tokens to be passed
tokens.each do |token|
return true unless ApiJob.find_by_token(token).empty?
end
false
end
Now, within your refresh_users_list and loop, you can build new tokens and call job_exists? to check if you have queued jobs for the API. For example
# Build a token
def refresh_users_list
apis_array.each do |api|
token = ApiJob.token(attr1, attr2, attr3)
next if job_exists?(token)
api.myclass.new.delay.get_and_create_or_update_users
end
end
Note: Again I want to point out, you won't be able to just drop in the code above and have it work. You must tailor it to your application and the job's you're running.
Why is this so complicated?
From my research, there's no way to "tag" or uniquely identify a queued job through what delayed_job provides. Sure, each job has a unique :id attribute. You could store the ID values for each created job in some hash somewhere
{
"FacebookApi": [1, 4, 12],
"TwitterApi": [3, 193, 44],
# ...
}
and then check corresponding hash key for an ID, but I find this limiting, and not always sufficient for the problem When you need to identify a specific job by multiple attributes like above, we must create a way to find these jobs (without loading every job into memory and looping over them to see if one matches our criteria).
How is this working?
The Struct that the ApiJob extends has a :token attribute. This token is based on the attributes passed (attr1, attr2, attr3) and is built when a new class extending ApiJob is instantiated.
The find_by_token class method simply searches the string representation of the job in the delayed_job queue for a match based on a token built using the same token class method.
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.