Rails 4 array.include? issue - ruby-on-rails

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.

Related

Implementing simpler find_by_[attribute] code in Rails

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

Object does not get loaded

This is the weirdest thing ever happened to me with ruby/rails.
I have a model, Store, which has_many Balances. And I have a method that gives me the default balance based on the store's currency.
Store model.
class Store < ActiveRecord::Base
has_many :balances, as: :balanceable, dependent: :destroy
def default_balance
#puts self.inspect <- weird part.
balances.where(currency: self.currency)[0]
end
...
end
Balance model.
class Balance < ActiveRecord::Base
belongs_to :balanceable, :polymorphic => true
...
end
Ok, so then in the Balance controller I have the show action, that will give me a specific balance or the default one.
Balance controller.
class Api::Stores::BalancesController < Api::Stores::BaseController
before_filter :load_store
# Returns a specific alert
# +URL+:: GET /api/stores/:store_id/balances/:id
def show
#puts #store.inspect <- weird part.
#balance = (params[:id] == "default") ? #store.default_balance : Balance.find(params[:id])
respond_with #balance, :api_template => :default
end
...
private
# Provides a shortcut to access the current store
def load_store
#store = Store.find(params[:store_id])
authorize! :manage, #store
end
end
Now here is where the weird part comes...
If I make a call to the show action; for example:
GET /api/stores/148/balances/default
It returns null (because the currency was set as null, and there is no Balance with null currency), and the SQL query generated is:
SELECT `balances`.* FROM `balances` WHERE `balances`.`balanceable_id` = 148 AND `balances`.`balanceable_type` = 'Store' AND `balances`.`currency` IS NULL
So I DON'T know why... it is setting the currency as NULL. BUT if in any where in that process I put
puts #store.inspect
or inside the default_balance method:
puts self.inspect
it magically works!!!.
So I don't know why is that happening?... It seems like the store object is not getting loaded until I "inspect" it or something like that.
Thanks
Sam and Adrien are on the right path.
ActiveRecord overrides method_missing to add a whole bunch of dynamic methods including the accessors for the column-backed attributes like Store#currency. While I'm glossing over a lot, suffice it to say that when the logic is invoked then the dynamic class/instance methods are added to the Store class/instances so that subsequent calls no longer require the method_missing hook.
When YOU overrode method_missing without calling super, you effectively disabled this functionality. Fortunately, this functionality can be invoked by other means, one of which you tripped upon when you called store#inspect.
By adding the call to super, you simply assured that ActiveRecord's dynamic methods are always added to the class when they're needed.
OK finally after a lot of debugging, I found the reason...
In the Store model I have a method_missing method and I had it like this:
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
end
end
So when I was calling self.currency it went first to the method_missing and then returned null. What I was missing here was the super call.
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
else
super
end
end
But I continue wondering why after I had called puts #store.inspect or puts self.inspect it worked well?. I mean, why in that case that super call wasn't needed?

How do I modify Rails ActiveRecord query results before returning?

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

Ruby/Rails: Is it possible to execute a default method when calling an instance (#instance == #instance.all IF "all" is the default method)?

I understand my question is a bit vague but I don't know how else to describe it. I've asked in numerous places and no one seems to understand why I want to do this. But please bear with me, and I'll explain why I want something like this.
I'm using Liquid Templates to allow users to make some dynamic pages on my site. And for those that don't know, Liquid uses a class of theirs called LiquidDrop to expose certain items to the user. Any method in the drop can be called by the Liquid template.
class PageDrop < Liquid::Drop
def initialize(page)
#page = page
end
def name
#page.name
end
def children
PagesDrop.new(#page.children)
end
end
class PagesDrop < Liquid::Drop
def initialize(pages)
#pages = pages
end
def group_by
GroupByDrop.new(#pages)
end
def all
#pages.all
end
def size
#pages.size
end
end
For example, I want to be able to do this:
#page_drop = PageDrop.new(#page)
#page_drop.children # to get an array of children
instead of
#page_drop.children.all
Why do I have a pages drop?
Because I want to be able to cleanly split up the methods I can do to an array of pages, and methods I can do to a single page. This allows me to group pages like so:
#page_drop.children.group_by.some_method_here_that_the_group_drop_contains
To make it simpler for my users, I don't want them to have to think about adding "all" or not to a drop instance to get the "default" object/s that it contains. To reiterate:
#pages_drop = PagesDrop.new(Page.all)
#pages_drop == #pages_drop.pages #I want this to be true, as well as
#pages_drop == #pages_drop.all
Where did I get this idea?
In Rails, a scope (association object) (#person.friends) seems to return the array when you do certain things to it: #person.friends.each, for person in #person.friends
This isn't really possible. When you write #instance you aren't really calling an instance as you describe, you're getting a reference to the object that #instance refers to.
The reason it seems to work with the collections for Rails' associations is that the the association objects are instances of Array that have had some of their methods overridden.
I would consider removing PagesDrop and using the group_by(&:method) syntax if you want a concise way to express groupings. If you do want to keep it then you can get some way towards what you want by implementing each and [] on PagesDrop and having them delegate to #pages. That will let you use #page_drop.children in for loops, for instance.
It looks like you want to implement has_many outside of rails. Will the following work?
class PageDrop < Liquid::Drop
attr_accessor :children
def initialize(page)
#page = page
#children = []
end
def name
#page.name
end
end
This allows you to do the following:
#page_drop = PageDrop.new(#page)
#page_drop.children.size # => 0
#page_drop.children # => []
This also gives you all the standard array functions (group_by, size, each, etc). If you want to add your own methods, create a class that inherits from Array and add your methods there.
class PageArray < Array
def my_method
self.each{|a| puts a}
end
end
class PageDrop < Liquid::Drop
attr_accessor :children
def initialize(page)
#page = page
#children = PageArray.new
end
[...]
end
#page_drop = PageDrop.new(#page)
#page_drop.children.size # => 0
#page_drop.children # => []
#page_drop.children.my_method # Prints all the children
Then any functions you don't define in PageArray fall through to the Ruby Array methods.

Refactor ruby helper method

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

Resources