I have a method in patch as below:
def applicable_resource_type(resource_type)
if resource_type.include?('Student')
student_setting
else
teacher_setting
end
end
This method is called in another patch which checks whether the resource type is 'teacher' or 'student' and stores the boolean value in 'active'.
def exams
School.new(resource: self).request if can_examine_students?
end
private
def can_examine_students?
active = applicable_resource_type(self.class.name).is_active?
if active && (self.is_a?(Teacher))
active = belongs_to_school?
end
active
end
However the resource_type is passed as a String whereas in can_examine_students? it is passed as a class/module. Is there any way to make them both consistent?
I tried the following:
def applicable_resource_type(resource_type)
if resource_type.include?(Student)
student_setting
else
teacher_setting
end
end
But it gave error as:
TypeError:
no implicit conversion of Class into String
I also tried
resource_type.include?('Student'.constantize)
But it gave error same typerror.
Is there a way to resolve the above error and keep both consistent resource_type consistent?
Thanks
Actually, in the second code snippet, when you call applicable_resource_type(self.class.name) you also hand over a String, because class.name returns a string.
If you want to write the first method more elegantly, you can use is_a? which accepts a class name as an argument. It would look like this:
def applicable_resource_type(resource_type)
if resource_type.is_a?(Student)
...
Note, that you pass Student as a class name.
You then have to adapt the second code snippet too and just pass the class and not class.name. Hence,
def can_examine_students?
active = applicable_resource_type(self.class).is_active?
...
Related
I need to pass a value to attribute in a model from a different controller with no direct relation between them. In the below example I need to update farming_year in the Field Model from the Planting controller.
The Field model:
class Field < ApplicationRecord
has_many :crops
attr_accessor :farming_year
def getting_crops
#crops_list = Crop.select('crops.name').where(field_id: self.id, year: self.get_farming_year) # doesn't get the farming_year
end
def get_farming_year
#farming_year # passing the value directly will work #farming_year=2015!!
end
def farming_year=(val)
#farming_year = val # passing the value directly won't work #farming_year=2015!!
end
end
In the Planting controller:
def new
#field = Field.new
#field.farming_year = session[:working_year]
#field.save
flash.now[:success] = #field.get_farming_year # it works and gives the correct year
end
when I changed the #farming_year in the get_farming_year method to #farming_year=2016, then the code will work and will give the correct Crops records. the flash message in the code above without any change will give the correct year from the model. I think my main issue is passing the farming year from get_farming_year method to getting_crops method.
Hint: the framing year is belong to the Crop not to the Field, so I don't need to add it to the Field table.
Any ideas how to achieve that?
Your code has a number of issues.
attr_accessor
Why are you using an attr_accessor? You should store the value on a model attribute, in the database. If your Field model doesn't already have a farming_year attribute, create a migration to add it to the database by running these commands:
$ rails g migration AddFarmingYearToField farming_year:integer
$ rails db:migrate
If you're running Rails <= 4, use rake db:migrate instead of the second command.
Doing this means you don't need to use attr_accessor, or define getters and setters.
PlantingController#new
This method isn't working for you because you haven't defined the correct methods, and you're not saving the instance.
In your Field model, you've defined a farming_year method, but you haven't defined a farming_year= method, which is what the setter should be. Change your farming_year method definition to farming_year=. Alternatively, use the method I described in 1., then you won't have to.
Make sure you're saving the model object once you're done with it - call Field#save, which returns truthy on success and falsy on failure; or call Field#save!, which returns truthy on success and raises an exception on failure.
The main issue with my code was using attr_accessor which I didn't need it, so, I've replaced "attr_accessor :farming_year" with a class variable "##work_year =''", and updated the getter and setter method as in the below code
The Field model:
class Field < ApplicationRecord
has_many :crops
attr_accessor :farming_year
##work_year =''
def getting_crops
#crops_list = Crop.select('crops.name').where(field_id: self.id, year: farming_year) #now this can request the getter method and get the year
end
def farming_year # getter method
##work_year ||= ''
end
def farming_year=(val) #setter method
##work_year = val
end
end
In the Planting controller:
def new
#field = Field.new
#field.farming_year = session[:working_year]
##field.save NO need for this line
flash.now[:success] = #field.farming_year
end
Thank you all for your kind support:)
I was looking at this code and was trying to figure what def status=(status) means. I have never seen that before.
class Tweet
attr_accessor :status
def initialize(options={})
self.status = options[:status]
end
def public?
self.status && self.status[0] != "#"
end
def status=(status)
#status = status ? status[0...140] : status
end
end
I'll try answering this in layman's terms, since I didn't understand this when starting out.
Let's say you want the Tweet class to have an attribute status. Now you want to change that attribute, well you can't since it's hidden inside the class. The only way you can interact with anything inside a class is by creating a method to do so:
def status=(status)
#status = status # using # makes #status a class instance variable, so you can interact with this attribute in other methods inside this class
end
Great! Now I can do this:
tweet = Tweet.new
tweet.status = "200" # great this works
# now lets get the status back:
tweet.status # blows up!
We can't access the status variable since we haven't defined a method that does that.
def status
#status # returns whatever #status is, will return nil if not set
end
Now tweet.status will work as well.
There are shorthands for this:
attr_setter :status #like the first method
attr_reader :status # like the second one
attr_accessor :status # does both of the above
That is a setter - the method to be called when you say thing.status = whatever.
Without such a method, saying thing.status = whatever would be illegal, since that syntax is merely syntactic sugar for calling the setter.
It means exactly the same thing that def foo always means: define a method named foo.
def initialize
Defines a method named initialize.
def public?
Defines a method named public?
def status=
Defines a method named status=
That's it. There's absolutely nothing special going on here. There is no magic when defining a method whose name ends in an = sign.
The magic happens when calling a method whose name ends in an = sign. Basically, you are allowed to insert whitespace in between the = sign and the rest of the method name. So, instead of having to call the method like this
foo.status= 42
You can call it like this:
foo.status = 42
Which makes it look like an assignment. Note: it is also treated like an assignment in another way; just like with all other forms of assignments, assignment expressions evaluate to the value that is being assigned, which means that the return value of the method is ignored in this case.
I have many different classes under /lib/ folder with many actions.
Before saving an object I need to call a method from a class that matches its name with an attribute inside the object i.e. given this
User.gateway = "something"
I need to call myfunction from something class before the object is saved.
Not sure how to do this.
your question is quite ambiguous,is this what you need?
# user.rb
before_save :myfunction
protected
def myfunction
g = self.gateway
case g
when String | Symbol
begin
g.classify.constantize.myfunction
rescue NameError
# if there is no something class
end
else
# no good value
end
end
enter code here
constantize and classify will do the job for you. Suppose you have:
class Foo
end
and the "foo" string. You can do:
"foo".classify.constantize.new.myfunction
I'd appreciate any help I can get with a somewhat strange phenonemon going on in my code. The controller's create method is (roughly) as follows:
def create
#session ||= Session.new
#session.date = params[:date]
#session.generate_string
#session.save
# etc
end
And the model:
class Session < ActiveRecord::Base # table is 'sessions' with 3 columns :id, :str, :date
include MyHelper
def generate_string(num_chars)
#str ||= ""
num_chars.to_i.times do
#str += some_method_in_MyHelper() # method returns a string
end
end
end
With some logging I found out that although the generate_string is working correctly, the resulting #session (in the controller) has the date set as expected but the value of str is a blank string. Sure enough, when the .save is hit, the database is told to insert a record consisting of a blank string and the correct date.
I found this Why do my changes to model instances not get saved sometimes in Rails 3? that suggests I should be using the "self" prefix instead of #. This seems to make the code work as expected, but seems strange because I thought self.xxx referred to the class, not the class instance. I'd be grateful if anyone could clarify what's going on - thanks!
self refers to the instance when used inside an instance method. It refers to the class outside an instance method, when it (self) is the class that's being defined.
# is an instance variable, which is different than an ActiveRecord column.
In order to store it in the str field to be saved to the database, you need to use self.str method. I think this is what you are looking for
class Session < ActiveRecord::Base # table is 'sessions' with 3 columns :id, :str, :date
include MyHelper
def generate_string(num_chars)
str = ""
num_chars.to_i.times do
str += some_method_in_MyHelper() # method returns a string
end
self.str = str # store it in attribute to be saved into db
end
end
Notice I removed the instance variable #str and changed it to local variable str instead because it seems like you want to generate a new string everytime this method is called? Also, this variable caching is useless
#session ||= Session.new
because instance variables only stick around for a single request. It should be
#session = Session.new
I have a name attribute on a Person model and every time I access the name attribute, I want name.capitalize to be returned.
Doing the following inside the model won't work,
def name
name.capitalize
end
so what is the alternative?
I suggest you to create a secondary method with your custom formatters.
class Person
def formatted_name
name.capitalize
end
end
This is a better solution compared with overwriting the default implementation because setter and getters might called when updating/writing/saving the record to the database.
I remember once when I overwrote the default implementation of an attribute and each time a record was saved, the attribute was updated with the formatted value.
If you want to follow this way you can use alias_method_chain or take advantage of inheritance including an external module.
class Person
def name_with_formatter
name_without_formatter.capitalize
end
alias_method_chain :name, :formatter
end
Also you can overwrite name and call read_attribute(:name) from within your custom method.
def name
read_attribute(:name).capitalize
end
def name
self[:name].capitalize
end
Again, don't do this. Go ahead and create a custom method.
But happens when name is null?
capitalize will throw an undefined method for nil::Class if self[:name] returns nil.
The following covers that:
def name
self[:name] ? self[:name].capitalize : nil
end
But I agree that you should create the formatted method and leave the name method as is. Never know when you might need the raw data.
FYI: the reason why your method didn't work was because it was causing, what I like to call, a self referring loop. You are redefining the method but calling the method in the new method. So you have to use self[:name] or read_attribute to get to the internal model data.
Try this:
def name
self[:name].capitalize
end