private method `catch' called for Hotel (ruby 2.1 rails 4) - ruby-on-rails

There's a column named catch in hotels table.
I created an object:
obj_hotel = Hotel.new
and then :
obj_hotel.catch = 'xxx'
Error occurred when I did the following thing:
puts obj_hotel.catch
There is no problem when obj_hotel.catch at the left of =, but when we want to use the value of obj_hotel.catch, I get the error private method 'catch' called for Hotel happened.
So, is catch a private method of rails?
Thank you.

Kernel#catch is a private method, and Kernel is present in the ancestor chain of all objects that descent (including) Object.

Ah, it would definitely seem so. As pointed out by azlazarov, Kernel#catch is a private method. You should avoid having column-names matching with ruby reserved names. So if possible rename the column (migrate the database).
If that is not possible, there is an easy workaround, you can always use
obj_hotel["catch"]
to get or set an attribute.
You can also alias an attribute name, using
alias_attribute :new_column_name, :catch
which is a very clean solution, but also potentially dangerous/confusing, imho, because when writing queries (in arel, e.g. a where) you will have to refer to catch instead of your alias).

Related

Ruby error - Undefined Method

I am try to write a function that will find the items in an array which match the string passed to the function. See code below.
class Island
def filter(string)
for element in self
if element.include? (string)
yield(element)
end
end
end
end
list = ["sasha","rory","rob","anthony","andre","tariq","kimberly","antoinette"]
list.filter("an"){|i| puts i}</i>
How i keep getting "undefined method 'filer' for #
I'm not sure what i'm doing wrong.
First let me object against the solution posted by #Sravan :
While it is true - and sometimes even a good solution - to monkey-patch a class, you have to be careful how to do it, because it may become a time bomb:
Ruby evolves, and new versions often add methods to existing classes. This means that if you add a method Array#search, and a new version of Ruby will also add a method of the same name, your new method will SILENTLY override the one in Ruby. You likely won't notice it for long time, until you are using a feature which is supposed to use Rubys Array#search - maybe by using something new in stdlib - and you get weird results. To track down this type of error can be a nightmare. This is exactly the case when you use search as a method name.
Now, how to do it then? Three possibilities:
(1) If you monkey-patch, use at least a method name which is unlikely to become part of the official interface. It might have your project's name as a prefix, or plenty of underscore characters, and so on. Note that this is not 100% foolproof: A later version of Ruby might add under the hood a private method with exactly the same name than the one you were choosing, but of course the odder your name, the less likely this will happen.
(2) If you don't like this idea of using "clumsy" names, you could at least test before defining the new method, whether it already exists, and throw an exception if it doesn't:
class Array
if self.method_defined?(:search)
raise "#{self.class}::search already defined"
else
def search(...)
...
end
end
end
(3) The third possibility is to avoid monkey-patching and keep the method in your Island class. In this case, the method definition would be different:
class Island
def self.filter(array, string)
...
end
end
and it would be called by
Island.filter(myarray, mystring)
UPDATE: Forgot a forth possibility:
(4) You can make Island a subclass of Array. I don't know what else you want to do with your islands, but maybe this is an option worth considering:
class Island < Array
def filter(string)
...
end
end
Of course, when invoking filter, you need to turn your array into an island, before you can use it:
list = Island.new([....])
Following ruby's convention over configuration, you can add/overwrite any method in any class
So, adding a function to array class makes it accessible to all the arrays. So, in this solution.
1) First thing is you have taken the filter function in Island class, instead, you need to take inside Array class since the list is an array.
class Array
def filter(string)
for element in self
if element.include? (string)
yield(element)
end
end
end
end
list = ["sasha","rory","rob","anthony","andre","tariq","kimberly","antoinette"]
list.filter("an"){|i| puts i}
O/P:
anthony
andre
antoinette
2) Since Filter is a keyword as suggested by other answer, take another name for it. Eg: search
class Array
def search(string)
for element in self
if element.include? (string)
yield(element)
end
end
end
end
list.search("an"){|i| puts i}

Rails common method for updating a database field

I am new to rails and I have a task to write a common method that will update a specific database field with a given value. And I should be able to invoke the method from anywhere in the app.(I understand about the security flaw and so on.. But I was asked to do it anyway) In my application controller I tried
def update_my_model_status(model,id,field, value)
#model = model.find(id)
#model.update(field: value)
end
Of course this doesn't work.. How to achieve this? What is the right way to do this? And if it is possible how to pass a model as an argument to a method?
If you're using Rails, why not use Rails?
Compare update_all:
MyModel.where(id: 1).update_all(banned: true)
or maybe update_attribute:
my_model.update_attribute(:banned, true)
to:
update_my_model_status(MyModel, 1, :banned, true)
Notice how, despite being shorter, the first two approaches are significantly more expressive than the last - it is much more obvious what is happening. Not only that, but they are immediately more familiar to any Rails developer off the street, while the custom one has a learning curve. This, combined with the added code from the unnecessary method adds to the maintenance cost of the application. Additionally, the Rails methods are well tested and documented - are you planning to write that, too? Finally, the Rails methods are better thought out - for example, your prototype naively uses attribute validations, but does not check them (which could result in unexpected behavior) and makes more SQL queries than it needs to. It's fine to write custom methods, but let's not write arbitrary wrappers around perfectly fine Rails methods...
Try this:
def update_my_model_status(model,id,field, value)
#model_var = model.capitalize.constantize.find(id)
#model_var.update_attributes(field: value)
end
Instead of just using update you should use update_attributes:
def update_my_model_status(model,id,field, value)
#model_var = model.find(id)
#model.update_attributes(field: value)
end
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update

Override ActiveRecord::Base find method (to accept non-default id field as a search parameter)

I have followed this tutorial on how to accept not-only-numeric primary key id when creating instance of my ModelName in my Ruby on Rails application.
Everything is okay, but there is a paragraph:
Be aware that Product.find won’t work anymore, and other Rails helper that relies on id will stop functioning. If you really want that, you need to override more methods and this seems too much of a pain for me. So I’d highly recommend you to leave #id as is.
The question is: when I am trying to get instance of my model by using .find() method in my ModelNameController it doesn't work (I think that's because of .find() method's search parameters - it does find something by id field which is numeric)
I have this piece of code:
def set_model_name
#model_name = ModelName.find(params[:hashid])
end
Where :hashid is a parameter that is a string (I'd like to use a string instead of a number)
How could I solve my problem?
One of the solutions would be overriding ActiveRecord::Base's .find() method.
Thanks in advance!
You do not need to override the default behaviour of find. Instead, you can use the find_by method:
def set_model_name
#model_name = ModelName.find_by(hashid: params[:hashid])
end

How to save nil into serialized attribute in Rails 4.2

I am upgrading an app to Rails 4.2 and am running into an issue where nil values in a field that is serialized as an Array are getting interpreted as an empty array. Is there a way to get Rails 4.2 to differentiate between nil and an empty array for a serialized-as-Array attribute?
Top level problem demonstration:
#[old_app]
> Rails.version
=> "3.0.3"
> a = AsrProperty.new; a.save; a.keeps
=> nil
#[new_app]
> Rails.version
=> "4.2.3"
> a = AsrProperty.new; a.save; a.keeps
=> []
But it is important for my code to distinguish between nil and [], so this is a problem.
The model:
class AsrProperty < ActiveRecord::Base
serialize :keeps, Array
#[...]
end
I think the issue lies with Rails deciding to take a shortcut for attributes that are serialized as a specific type (e.g. Array) by storing the empty instance of that type as nil in the database. This can be seen by looking at the SQL statement executed in each app:
[old_app]: INSERT INTO asr_properties (lock_version, keeps)
VALUES (0, NULL)
Note that the above log line has been edited for clarity; there are other serialized attributes that were being written due to old Rails' behavior.
[new_app]: INSERT INTO asr_properties (lock_version)
VALUES (0)
There is a workaround: by removing the "Array" declaration on the serialization, Rails is forced to save [] and {} differently:
class AsrProperty < ActiveRecord::Base
serialize :keeps #NOT ARRAY
#[...]
end
Changing the statement generated on saving [] to be:
INSERT INTO asr_properties (keeps, lock_version) VALUES ('---[]\n', 0)
Allowing:
> a = AsrProperty.new; a.save; a.keeps
=> nil
I'll use this workaround for now, but:
(1) I feel like declaring a type might allow more efficiency, and also prevents bugs by explicitly prohibiting the wrong data type being stored
(2) I'd really like to figure out the "right" way to do it, if Rails does allow it.
So: can Rails 4.2 be told to store [] as its own thing in a serialized-as-Array attribute?
What's going on?
What you're experiencing is due to how Rails 4 treats the 2nd argument to the serialize call. It changes its behavior based on the three different values the argument can have (more on this in the solution). The last branch here is the one we're interested in as when you pass the Array class, it gets passed to the ActiveRecord::Coders::YAMLColumn instance that is created. The load method receives the YAML from the database and attempts to turn it back into a Ruby object here. If the coder was not given the default class of Object and the yaml argument is nil in the case of a null column, it will return a new instance of the class, hence the empty array.
Solution
There doesn't appear to be a simple Rails-y way to say, "hey, if this is null in the database, give me nil." However looking at the second branch here we see that we can pass any object that implements the load and dump methods or what I call the basic coder protocol.
Example code
One of the members of my team built this simple class to handle just this case.
class NullableSerializer < ActiveRecord::Coders::YAMLColumn
def load(yaml)
return nil if yaml.nil?
super
end
end
This class inherits from the same YAMLColumn class provided by ActiveRecord so it already handles the load and dump methods. We do not need any modifications to dump but we want to slightly handle loading differently. We simply tell it to return nil when the database column is empty and otherwise call super to work as if we made no other modification.
Usage
To use it, it simply needs to be instantiated with your intended serialization class and passed to the Rails serialize method as in the following, using your naming from above:
class AsrProperty < ActiveRecord::Base
serialize :keeps, NullableSerializer.new(Array)
# …
end
The "right" way
Getting things done and getting your code shipped is paramount and I hope this helps you. After all, if the code isn't being used and doing good, who cares how ideal it is?
I would argue that Rails' approach is the right way in this case especially when you take Ruby's philosophy of The Principle of Least Surprise into account. When an attribute can possibly be an array, it should always return that type, even if empty, to avoid having to constantly special case nil. I would argue the same for any database column that you can put a reasonable default on (i.e. t.integer :anything_besides_a_foreign_key, default: 0). I've always been grateful to past-Aaron for remembering this most of the time whenever I get an unexpected NoMethodError: undefined method 'whatever' for nil:NilClass. Almost always my special case for this nil is to supply a sensible default.
This varies greatly on you, your team, your app, and your application and it's needs so it's never hard and fast. It's just something I've found helps me out immensely when I'm working on something and wondering if amount could default to 0 or if there's some reason buried in the code or in the minds of your teammates why it needs to be able to be nil.

Remove element from ActiveRecord_Relation in rails

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))

Resources