I have a helper method which checks whether the collection of objects is empty? If not then it checks each one to make sure that the the existing event_id is not the #current_event.id.
Here is my crack at it:
def build_answers(question)
if question.answers.empty?
return question.answers.build
else
question.answers.each do |a|
if a.event_id != #current_event.id
return question.answers.build
end
end
end
end
Update: This helper method sets the form to build new children objects if the conditions pass. I've updated the example above. btw, it doesn't need to be a single line. I just wanted something cleaner than what I have above.
Without knowing what you're actually doing inside the blocks, it's difficult to give the best solution.
For the most part, all you could really do is select before executing the logic on the filtered collection, rather than testing the logic in the block.
e.g.
uncurrent_answers = questions.answers.select{|a| a.event_id != #current_event.id}
uncurrent_answers.each do |a|
#do whatever
end
IMHO it's a little bit neater, and perhaps more rubyish..
Well, I don't know why would you want to put conditions into a single line, but the else block could be rewritten into this:
question.answers.select {|answer| answer.event_id != #current_event.id }.each
{|ans| #.. logic with answer here }
I think you current method is responsible for too many things, my idea is to create a clase with a single responsibility of building answers. That would make your code more readable and also easy to test. A posible implementation would look something like :
def build_answers(question)
AnswerBuilder.build(question.answers, #current_event)
end
class AnswerBuilder
def initialize(answers, current_event)
#answers = answers
#current_event = current_event
end
def self.build(answers, current_event)
new(answers, current_event).build
end
def build
if answers.empty?
answers.build
else
create_allowed_answers
else
end
private
attr_reader :answers, :current_event
def create_allowed_answers
answers.each do |a|
if a.event_id != current_event.id
return answers.build
end
end
end
end
Related
Hi i have a function to check if the current controller + action is included in array list but its not working here is my code
Function
def verify
cur_path = params[:controller] +'/'+ params[:action]
confirm_access(cur_path)
end
def confirm_access(section)
user_group = UserGroup.find(1)
allowed_acl = Array.new
user_group.access_sections.each do |d|
allowed_acl << d.section
end
if allowed_acl.include? section
return true
else
#false
end
end
But when I pass confirm_access("string") static string it is working but not with cur_path variable.
It's really hard to figure out what is not going right. On the surface, it seems like it should be working fine.
I wonder if you simplified the code a bit then you might be able to debug things better.
For one, you could add a method to UserGroup that gives you the acls you want.
class UserGroup < ActiveRecord::Base
...
def allowed_acl
access_sections.map(&:section)
end
end
Then your controller methods become much simpler
def verify
cur_path = params[:controller] +'/'+ params[:action]
confirm_access(cur_path)
end
def confirm_access(section)
UserGroup.find(1).allowed_acl.include? section
end
It seems like at this point, you should be able to look at the list of allowed ACL's and make sure that the match you expect to happen is happening. I mocked this up (outside of Rails) and it all seems to be working fine.
I am trying to display a model attribute only if it is present. If it is not, then a placeholder should be displayed. This is what I've got:
class Person < ActiveRecord::Base
def name
if self.name.blank?
"[You have no name yet]"
else
read_attribute(:name)
end
end
end
However, I am getting a stack level too deep error.
How can this be done?
Thanks for any help.
I agree with Ishank but you can call super to use Rails' getter and then use ActiveSupport's presence method which will return the value if it is present? or otherwise return nil (which will trigger the statement after the ||).
def name
super.presence || "[You have no name yet]"
end
To be clear, stack level too deep is happening because you are checking self.name.blank? - when you use self.name here, that is calling the name method on self (which is the method you are currently in) - so that results in an infinite loop.
This should not be a part of Model. You should write this method in your views.
You can have something like #person.name || "You have no name yet"
You are getting exception stack level too deep because read_attribute[:name] again calls the name method.
Also a thing to keep in mind for using self. According to Ruby style guide:
Avoid self where not required. (It is only required when calling a
self write accessor.)
# bad
def ready?
if self.last_reviewed_at > self.last_updated_at
self.worker.update(self.content, self.options)
self.status = :in_progress
end
self.status == :verified
end
# good
def ready?
if last_reviewed_at > last_updated_at
worker.update(content, options)
self.status = :in_progress
end
status == :verified
end
Can you try this for stack too deep?
def name
if self.first.name.blank?
"[You have no name yet]"
else
read_attribute[:name]
end
end
Personally, i always do something like this, using self[:name] as a way of accessing the database rather than the .name method:
def name
if self[:name].blank?
"[You have no name yet]"
else
self[:name]
end
end
I am still using rails 2.2, so this may function differently for you.
Having said that, a cleaner and more transparent way to do it is to set "[You have no name yet]" as the default value for the name column in the database. Then you don't need to override the accessor method, which always feels a bit dirty to me.
It is not a good practise to include presentation logic in data models.
You should instead use decorators, view objects, or similar, or just do it in the view, but not in the model.
Examples using the Draper gem:
class PersonDecorator < Draper::Decorator
delegate_all
def name
object.name.presence || I18n.t('warnings.no_name_yet')
end
end
In the view:
<%= #person.name.presence || I18n.t('warnings.no_name_yet') %>
See the "Introduce View Objects" section in http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/.
I have a table of statuses, each of which have a name attribute. Currently I can do:
FooStatus.find_by_name("bar")
And that's fine. But I'm wondering if I could do:
FooStatus.bar
So I have this approach:
class FooStatus < ActiveRecord::Base
def self.method_missing(meth, *args, &block)
if self.allowed_statuses.include?(meth.to_s.titleize)
self.where("name = ?", meth.to_s.titleize).first
else
super(meth, *args, &block)
end
end
def self.allowed_statuses
self.pluck(:name)
end
end
The above code works, but it leads to the following weird behavior:
FooStatus.respond_to?(:bar) => false
FooStatus.bar => #<FooStatus name: 'bar'>
That's not great, but if I try to implement respond_to?, I get a recursion problem
class FooStatus < ActiveRecord::Base
def self.method_missing(meth, *args, &block)
if self.allowed_statuses.include?(meth.to_s.titleize)
self.where("name = ?", meth.to_s.titleize).first
else
super(meth, *args, &block)
end
end
def self.allowed_statuses
self.pluck(:name)
end
def self.respond_to?(meth, include_private = false)
if self.allowed_statuses.include?(meth.to_s.titleize)
true
else
super(meth)
end
end
end
And that gets me:
FooStatus.bar => ThreadError: deadlock; recursive locking
Any ideas on getting method_missing and respond_to to work together?
I agree with Philip Hallstrom's suggestion. If you know allowed_statuses when the class is built, then just loop through the list and define the methods explicitly:
%w(foo bar baz).each do |status|
define_singleton_method(status) do
where("name = ?", status.titleize).first
end
end
…or if you need that list of statuses elsewhere in the code:
ALLOWED_STATUSES = %w(foo bar baz).freeze
ALLOWED_STATUSES.each do |status|
define_singleton_method(status) do
where("name = ?", status.titleize).first
end
end
Clearer, shorter, and far less prone to future breakage and weird rabbit hole conflicts with ActiveRecord like the one you're in.
You can do really cool things with method_missing and friends, but it's not the first approach to go to when doing metaprogramming. Explicit is usually better when possible.
I also agree with Philip's concernt about creating conflicts with built-in methods. Having a hard-coded list of statuses prevents that from going too far, but you might consider a convention like FooStatus.named_bar instead of FooStatus.bar if that list is likely to grow or change.
I don't know if I'd recommend your approach... seems too magical to me and I worry about what happens when you have a status with a name of 'destroy' or some other method you might legitimately want to call (or that Rails' calls internally that you aren't aware of).
But... instead of mucking with method missing, I think you'd be better off extending the class and automatically defining the methods by looping through allowed_statuses and creating them. This will make respond_to? work. And you could also check to make sure it's not already defined somewhere else...
Use a scope.
class FooStatus < ActiveRecord::Base
scope :bar, where(:name => "bar")
# etc
end
Now, you can do FooStatus.bar which will return an ActiveRelation object. If you expect this to return a single instance, you could do FooStatus.bar.first or if many FooStatus.bar.all, or you could put the .first or .all on the end of the scope in which case it'll return the same thing as the finder.
You can also define a scope with a lambda if the input isn't constant (not always "bar"). Section 13.1 of this guide has an example
I have a table with data that needs to be updated at run-time by additional data from an external service. What I'd like to do is something like this:
MyModel.some_custom_scope.some_other_scope.enhance_with_external_data.each do |object|
puts object.some_attribute_from_external_data_source
end
Even if I can't use this exact syntax, I would like the end result to respect any scopes I may use. I've tried this:
def self.enhance_with_external_data
external_data = get_external_data
Enumerator.new do |yielder|
# mimick some stuff I saw in ActiveRecord and don't quite understand:
relation.to_a.each do |obj|
update_obj_with_external_data(obj)
yielder.yield(obj)
end
end
end
This mostly works, except it doesn't respect any previous scopes that were applied, so if I do this:
MyModel.some_custom_scope.some_other_scope.enhance_with_external_data
It gives back ALL MyModels, not just the ones scoped by some_custom_scope and some_other_scope.
Hopefully what I'm trying to do makes sense. Anyone know how to do it, or am I trying to put a square peg in a round hole?
I figured out a way to do this. Kind of ugly, but seems to work:
def self.merge_with_extra_info
the_scope = scoped
class << the_scope
alias :base_to_a :to_a
def to_a
MyModel.enhance(base_to_a)
end
end
the_scope
end
def self.enhance(items)
items.each do |item|
item = add_extra_item_info(item)
end
items
end
What this does is add a class method to my model - which for reasons unknown to me seems to also make it available to ActiveRecord::Relation instances. It overrides, just for the current scope object, the to_a method that gets called to get the records. That lets me add extra info to each record before returning. So now I get all the chainability and everything like:
MyModel.where(:some_attribute => some_value).merge_with_extra_info.limit(10).all
I'd have liked to be able to get at it as it enumerates versus after it's put into an array like here, but couldn't figure out how to get that deep into AR/Arel.
I achieved something similar to this by extending the relation:
class EnhancedModel < DelegateClass(Model)
def initialize(model, extra_data)
super(model)
#extra_data = extra_data
end
def use_extra_data
#extra_data.whatever
end
end
module EnhanceResults
def to_a
extra_data = get_data_from_external_source(...)
super.to_a.map do |model_obj|
EnhancedModel.new(model_obj, extra_data)
end
end
end
models = Model.where('condition')
models.extend(EnhanceResults)
models.each do |enhanced_model|
enhanced_model.use_extra_data
end
I have a piece of code here that i really could use some help with refactoring. I need the different methods for adding relational data in a form in rails. The code is taken from http://railscasts.com/episodes/75-complex-forms-part-3, my problem is that i need to have the methods fro both the Material model and the Answer model. So i need the exact same code twice with "materials" replaced by "answers".
It seems this should be solved with some dynamic programming? But I have no experience at all with that.
How is this solved?
after_update :save_materials
after_update :save_answers
def new_material_attributes=(material_attributes)
material_attributes.each do |attributes|
materials.build(attributes)
end
end
def existing_material_attributes=(material_attributes)
materials.reject(&:new_record?).each do |material|
attributes = material_attributes[material.id.to_s]
if attributes
material.attributes = attributes
else
materials.delete(material)
end
end
end
def save_materials
materials.each do |material|
material.save(false)
end
end
You might also want to take a look at this site:
http://refactormycode.com/
If I understood you correctly, you want to have the same methods for answers as for materials, but duplicating the least code. The way to do this is by abstracting some private methods that will work for either answers or materials and call them with the appropriate model from the methods specific to those models. I've given a sample below. Note that I didn't do anything with the save_ methods because I felt they were short enough that abstracting them really didn't save much.
after_update :save_materials
after_update :save_answers
// Public methods
def new_material_attributes=(material_attributes)
self.new_with_attributes(materials, material_attributes)
end
def new_answer_attributes=(answer_attributes)
self.new_with_attributes(answers, answer_attributes)
end
def existing_material_attributes=(material_attributes)
self.existing_with_attributes(materials, material_attributes)
end
def existing_answer_attributes=(answer_attributes)
self.existing_with_attributes(answers, answer_attributes)
end
def save_materials
materials.each do |material|
material.save(false)
end
end
def save_answers
answers.each do |answer|
answer.save(false)
end
end
// Private methods
private
def new_with_atttributes(thing,attributes)
attributes.each do |attribute|
thing.build(attribute)
end
end
def existing_with_attributes=(things, attributes)
things.reject(&:new_record?).each do |thing|
attrs = attributes[thing.id.to_s]
if attrs
thing.attributes = attrs
else
things.delete(thing)
end
end
end