I need to set the id parameter to a value if it is wasn't submitted with the form.
Is it ok to do something like this in Rails or does this violate any standards or cause possible issues?
if params[:cart][:cart_addresses_attributes]["0"][:id].blank?
params[:cart][:cart_addresses_attributes]["0"][:id] = 1234 #default id
end
My implementation works with this logic, but I am not sure if this is the proper way to handle the issue.
There's a chance [:record_type] is nil which will lead to an undefined method error when you attempt to call [:id] on nil. Additionally, I'd find it a bit weird to directly mutate params, even though you technically can do that. I'd consider using Strong Parameter processing methods like so (added a full action, which isn't in your sample, to give more context on how this would be used):
def create
#record_type = RecordType.new(record_type_params)
if record_type.save
redirect_to #record_type
else
render :new
end
end
def record_type_params
params.require(:record_type).permit(:id).reverse_merge(id: 1234)
end
The reverse_merge call is a way to merge the user-supplied parameters into your defaults. This accomplishes what you're after in what I would consider a more conventional way and doesn't mutate params.
def cart_params
params.require(:cart).permit(:cart_addresses_attributes => [:id]).tap do |p|
p[:cart_addresses_attributes]["0"][:id] ||= 1234
end
end
if params[:record_type][:id].nil? # or replace ".nil?" with "== nil"
params[:record_type][:id] = 1234
end
personally, this is the way I prefer to do it. Some ways are more efficient than others, but if that works for you I'd roll with it.
Related
I want to disable all link of users at a time after deactivating users. So, for that I wrote a code like this
def link_to(*user)
if user_link_disabled?(user.id)
return nil
else
super
end
end
def user_link_disabled?(user_id)
User.where(activation: false).pluck(:name).include?(user_id)
end
But I am getting this error
undefined method `id' for #<Array:0x007efee4667d00>
Could anyone please help me on this?
I would add a column to your users model:
deactivated => type boolean
user.deactivated? #will return true or false
In your view you can then use link_to_unless
link_to_unless(user.deactivated, name, options = {}, html_options = {}, &block)
I don't know the scope because you didnt display anymore info but it could just be user_id in your if statement or try id[params[:id] instead of user.id but I'm not sure with out more context.
IN following method
def user_link_disabled?(user_id)
User.where(activation: false).pluck(:name).include?(user_id)
end
You are going to pluck name from user table records but you are checking include? for user.id, I think you should pluck id instead of name.
First of all, I am not gonna comment on your preferred code/method for overriding the the link_to helper. There is not much context available for that.
But to solve the particular error you are getting:
Your are defining method like this def link_to(*user) .
Here *user means it is expecting an Array as argument to the method and using the Ruby splat(*), it is converting it to normal arguments.
So if you call this as link_to [1,2,3], it will be same as calling a method with 3 arguments. That is link_to (1,2,3) but the argument user will be an Array.
So in here if user_link_disabled?(user.id), you are calling a id on a Array data type. That's why you are getting an error.
Depending on your use, either remove the * from method definition,
or
Use looping, if you are going to pass multiple users data to method, like:
def link_to(*user)
user.each do |u|
if user_link_disabled?(u.id)
return nil
else
super
end
end
end
As I mentioned in beginning, I don't know much about the context. So can not comment about the right way but if I may suggest, then I would suggest to use a custom helper for all user routes. like below pseudo code:
def link_to_user(user)
deactivated = user.deactivated?
if deactivated
# render some disabled link
else
# render link
end
end
I'm trying to monkey patch ActiveRecord::FinderMethods in order to use hashed ids for my models. So for example User.find(1) becomes User.find("FEW"). Sadly my overwritten method doesn't get called. Any ideas how to overwrite the find_one method?
module ActiveRecord
module FinderMethods
alias_method :orig_find_one, :find_one
def find_one(id)
if id.is_a?(String)
orig_find_one decrypt_id(id)
else
orig_find_one(id)
end
end
end
end
Here's an article that discusses how to actually do what you want by overriding the User.primary_key method like:
class User
self.primary_key = 'hashed_id'
end
Which would allow you to call User.find and pass it the "hashed_id":
http://ruby-journal.com/how-to-override-default-primary-key-id-in-rails/
So, it's possible.
That said, I would recommend against doing that, and instead using something like User.find_by_hashed_id. The only difference is that this method will return nil when a result is not found instead of throwing an ActiveRecord::RecordNotFound exception. You could throw this manually in your controller:
def show
#user = User.find_by_hashed_id(hashed_id)
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
Finally, one other note to make this easier on you -- Rails also has a method you can override in your model, to_param, to tell it what property to use when generating routes. By default, of course, it users the id, but you would probably want to use the hashed_id.
class User
def to_param
self.hashed_id
end
end
Now, in your controller, params[:id] will contain the hashed_id instead of the id.
def show
#user = User.find_by_hashed_id(params[:id])
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
I agree that you should be careful when doing this, but it is possible.
If you have a method decode_id that converts a hashed ID back to the original id, then the following will work:
In User.rb
# Extend AR find method to allow finding records by an encoded string id:
def self.find(*ids)
return super if ids.length > 1
# Note the short-circuiting || to fall-back to default behavior
find_by(id: decode_id(ids[0])) || super
end
Just make sure that decode_id returns nil if it's passed an invalid hash. This way you can find by Hashed ID and standard ID, so if you had a user with id 12345, then the following:
User.find(12345)
User.find("12345")
User.find(encode_id(12345))
Should all return the same user.
In rails 4.x, strong_parameters require parameters to be explicitly permitted. Yet, in the following example, I do NOT get a ForbiddenAttributesError - why does :id not throw when in the show action even though it is not explicitly permitted?
def FooController
...
def show
#foo = Foo.find(params[:id]) # why no exception here?
end
private
def foo_params
params.require(:foo).permit(:name, :address) # note: No :id here
end
end
See: http://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters
"With strong parameters, Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted."
Doing a find is completely valid, and is, in fact, shown in the example in the documentation linked to, above.
Strong parameters are used only for assignment of attributes. You can freely search and perform other operations with any param, just not mass assignment.
You can see more in-depth explanation and examples in Rails Guides
For Rails, params[:id] outside from default params.
Query string:
www.example.com/foo/123?bar=1&baz=2
Request path:
www.example.com/foo/123 where 123 is params[:id]
Paramerts:
bar=1&baz=2 this can be permitted
If you pass 123 to parameters then you need permitted :id.
There is no need of explicitly permitting the :id unless you want to.Rails will do it implicitly.If want to check whether the :id is whitelisted or not,you can do puts params[:foo] after it is created or you can just see the log.you will see something like this
{id=>some_id, "name"=>"some_name", "adddress"=>"some_address"}
So,defining a Foo object like this
#foo = Foo.find(params[:id])
will not throw an exception.
Hope it helped!
I'm using this regex in my model to validate an URL submitted by the user. I don't want to force the user to type the http part, but would like to add it myself if it's not there.
validates :url, :format => { :with => /^((http|https):\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+).[a-z]{2,5}(:[0-9]{1,5})?(\/.)?$/ix, :message => " is not valid" }
Any idea how I could do that? I have very little experience with validation and regex..
Use a before filter to add it if it is not there:
before_validation :smart_add_url_protocol
protected
def smart_add_url_protocol
unless url[/\Ahttp:\/\//] || url[/\Ahttps:\/\//]
self.url = "http://#{url}"
end
end
Leave the validation you have in, that way if they make a typo they can correct the protocol.
Don't do this with a regex, use URI.parse to pull it apart and then see if there is a scheme on the URL:
u = URI.parse('/pancakes')
if(!u.scheme)
# prepend http:// and try again
elsif(%w{http https}.include?(u.scheme))
# you're okay
else
# you've been give some other kind of
# URL and might want to complain about it
end
Using the URI library for this also makes it easy to clean up any stray nonsense (such as userinfo) that someone might try to put into a URL.
The accepted answer is quite okay.
But if the field (url) is optional, it may raise an error such as undefined method + for nil class.
The following should resolve that:
def smart_add_url_protocol
if self.url && !url_protocol_present?
self.url = "http://#{self.url}"
end
end
def url_protocol_present?
self.url[/\Ahttp:\/\//] || self.url[/\Ahttps:\/\//]
end
Preface, justification and how it should be done
I hate it when people change model in a before_validation hook. Then when someday it happens that for some reason models need to be persisted with save(validate: false), then some filter that was suppose to be always run on assigned fields does not get run. Sure, having invalid data is usually something you want to avoid, but there would be no need for such option if it wasn't used. Another problem with it is that every time you ask from a model is it valid these modifications also take place. The fact that simply asking if a model is valid may result in the model getting modified is just unexpected, perhaps even unwanted. There for if I'd have to choose a hook I'd go for before_save hook. However, that won't do it for me since we provide preview views for our models and that would break the URIs in the preview view since the hook would never get called. There for, I decided it's best to separate the concept in to a module or concern and provide a nice way for one to apply a "monkey patch" ensuring that changing the fields value always runs through a filter that adds a default protocol if it is missing.
The module
#app/models/helpers/uri_field.rb
module Helpers::URIField
def ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
alias_method "original_#{field}=", "#{field}="
define_method "#{field}=" do |new_uri|
if "#{field}_changed?"
if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
new_uri = "#{default_protocol}://#{new_uri}"
end
self.send("original_#{field}=", new_uri)
end
end
end
end
In your model
extend Helpers::URIField
ensure_valid_protocol_in_uri :url
#Should you wish to default to https or support other protocols e.g. ftp, it is
#easy to extend this solution to cover those cases as well
#e.g. with something like this
#ensure_valid_protocol_in_uri :url, "https", "https?|ftp"
As a concern
If for some reason, you'd rather use the Rails Concern pattern it is easy to convert the above module to a concern module (it is used in an exactly similar way, except you use include Concerns::URIField:
#app/models/concerns/uri_field.rb
module Concerns::URIField
extend ActiveSupport::Concern
included do
def self.ensure_valid_protocol_in_uri(field, default_protocol = "http", protocols_matcher="https?")
alias_method "original_#{field}=", "#{field}="
define_method "#{field}=" do |new_uri|
if "#{field}_changed?"
if new_uri.present? and not new_uri =~ /^#{protocols_matcher}:\/\//
new_uri = "#{default_protocol}://#{new_uri}"
end
self.send("original_#{field}=", new_uri)
end
end
end
end
end
P.S. The above approaches were tested with Rails 3 and Mongoid 2.
P.P.S If you find this method redefinition and aliasing too magical you could opt not to override the method, but rather use the virtual field pattern, much like password (virtual, mass assignable) and encrypted_password (gets persisted, non mass assignable) and use a sanitize_url (virtual, mass assignable) and url (gets persisted, non mass assignable).
Based on mu's answer, here's the code I'm using in my model. This runs when :link is saved without the need for model filters. Super is required to call the default save method.
def link=(_link)
u=URI.parse(_link)
if (!u.scheme)
link = "http://" + _link
else
link = _link
end
super(link)
end
Using some of the aforementioned regexps, here is a handy method for overriding the default url on a model (If your ActiveRecord model has an 'url' column, for instance)
def url
_url = read_attribute(:url).try(:downcase)
if(_url.present?)
unless _url[/\Ahttp:\/\//] || _url[/\Ahttps:\/\//]
_url = "http://#{_url}"
end
end
_url
end
I had to do it for multiple columns on the same model.
before_validation :add_url_protocol
def add_url_protocol
[
:facebook_url, :instagram_url, :linkedin_url,
:tiktok_url, :youtube_url, :twitter_url, :twitch_url
].each do |url_method|
url = self.send(url_method)
if url.present? && !(%w{http https}.include?(URI.parse(url).scheme))
self.send("#{url_method.to_s}=", 'https://'.concat(url))
end
end
end
I wouldn't try to do that in the validation, since it's not really part of the validation.
Have the validation optionally check for it; if they screw it up it'll be a validation error, which is good.
Consider using a callback (after_create, after_validation, whatever) to prepend a protocol if there isn't one there already.
(I voted up the other answers; I think they're both better than mine. But here's another option :)
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.