Weird and inconsistent Model issues - ruby-on-rails

I feel like I'm missing something rather important in both circumstances, but I can't seem to figure either out:
1) I have a model named TestCase -
class TestCase < ActiveRecord::Base
belongs_to :test_suite
scope :queued, lambda { where("test_cases.suite_id IS NOT NULL") }
scope :assigned_to, lambda { |sid| where(:suite_id => sid) }
end
The controller can interact with it perfectly fine. When trying to display information from it in either the view or via the view helper such as TestCase.all, I get a NoMethodError (undefined method 'all') If I call it with ::TestCase.all, that works. I had a theory that it has something to do with the fact that it's associated to another model (belongs_to ...), I just can't find anything to confirm that or tell me why that happens.
2) On another project I have yet another model named Artwork. Again, it has associations (belongs_to). In this case, I can access it just fine in the view, and all the methods within it work fine for the controller except if I try to do dynamic method calls. In this case I have a simple toggle for -
#artwork = Artwork.find(params[:id])
value = params[:value].to_sym
#artwork.update_attributes(value => !#artwork.method(value).call)
That gives me a NoMethodError. However, if I add - if #artwork.respond_to?(value) - then it works as expected. Again, I can't figure out why.
Both items I get working using the mentioned methods, but again, I feel like I'm really missing something important here.

Re: problem 1 -- Don't call your model "TestCase". That conflicts with the Rails TestCase class.
Re: problem 2 -- That's an odd way of doing things. You might get it working by using
#artwork.send(value)
but keep in mind that a rogue user could pass in any method name through the form and wreak havoc.

Related

undefined method for #<Array

I have this inside User model
def self.home_opinions (user)
home_opinions = user.opinions
user.follows.find_each do |follow|
home_opinions+=(follow.opinions)
end
home_opinions.order_by_most_recent
end
I have this scope inside Opinion model
scope :order_by_most_recent, -> { includes(:author).order(created_at: :desc) }
It shows this error
undefined method `order_by_most_recent' for #<Array:0x00007eff64d076f8>
But when I try User.home_opinions(User.find(9)) inside rails console
It works
I have two questions
why It shows the error
What are the best practices for this code maybe using includes?
.order_by_most_recent will only work on an ActiveRecord::Relation.
When you call home_opinions = user.opinions you get a relation object back.
The problem comes when you call
home_opinions += follow.opinions
That action converts the relation into an array, and then .order_by_most_recent is no longer available.
If you can, you should try and get all relevant opinions in a single ActiveRecord call. That way, you'll have an ActiveRecord::Relation object which you can chain with other scopes – plus, you can do everything in a fixed number of database calls, rather than an extra call for every member of the follows association.
Try something like:
opinion_owner_ids = [user.id] + user.follow_ids
home_opinions = Opinion.where(user_id: opinion_owner_ids)
home_opinions.order_by_most_recent

Rails 4: how to use named scope with has_many associations

In my Rails 4 app Project (model) has_many Videos (model). I have a named scope in the videos model:
scope :live, where( is_deleted: 0, sent_to_api: 1 )
In one of my project views, I do this (project is an instance of Project):
project.videos.live.size
What I expect to get is the number of projects in that specific project but instead I get the number of videos in any project. It's as if .live is not returning a subset from .videos but rather replacing it.
I see it explained here that chaining named scopes with one another should be combined with logical AND but when applied to an "association method" [<--not sure the proper terminology for .videos in this context] that doesn't seem to be happening.
What's the right way to do this?
I believe it should read like this in Rails 4:
scope :live, -> { where(is_deleted: 0, sent_to_api: 1) }
The rails 4 docs and all examples in it show you passing in a callable object to the scope to ensure it gets called each time. If it doesn't work like this try implementing it as a class method and see how that works out for you.
http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html
I would just go for class methods and leave scopes behind. The syntax is much simpler because it's just like any other class method, including passing parameters to it.
Try:
def self.live
where( is_deleted: 0, sent_to_api: 1 )
end
And then:
project.videos.live.size
and see if it helps.
For more info, read here.

Serialized column by model in rails work correctly only after refresh

In my model I have:
class Log < ActiveRecord::Base
serialize :data
...
def self.recover(table_name, row_id)
d = Log.where(table_name: table_name, row_id: row_id).where("log_type != #{symbol_to_constant(:delete)}").last
row = d.data
raise "Nothing to recover" if d.nil?
raise "No data to recover" if d.data.nil?
c = const_get(table_name)
ret = c.create(row.attributes)
end
And in my controller I calling it as:
def index
Log.recover params[:t], params[:r]
redirect_to request.referer
end
The problem is, if I access this page for the first time, I am getting error specified below, but after refresh, is everything OK. Where can be problem?
undefined method `attributes' for #<String:0x00000004326fc8>
In data column are saved instances of models. For the first time column isn't properly unserialized, it's just yaml text. But after refresh everything is fine. That's confusing, what is wrong? Bug in rails?
It's not every time, sometimes in first access everything is okey.
Deyamlizing an object of class Foo will do funny things if there is no class Foo. This can quite easily happen in development becauses classes are only loaded when needed and unloaded when rails thinks they might have changed.
Depending on whether the class is loaded or not the YAML load will have different results (YAML doesn't know about rail's automatic loading stuff)
One solution worth considering is to store the attributes hash rather than the activerecord object. You'll probably avoid problems in the long run and it will be more space efficient in the long wrong - there's a bunch of state in an activerecord object that you probably don't care about in this case.
If that's not an option, your best bet is probably to make sure that the classes that the serialized column might contain are loaded - still a few calls to require_dependency 'foo' at the top of the file.

Machinist, how do I reference the object I'm making and pass it to an association? (AssociationTypeMismatch)

I'm trying to build factories for relatively complex models.
I have a Pressroom model, which belongs to Source, and Source has many Pressrooms. When creating the Source, if pressrooms is empty, an initial pressroom is created in an after_create filter.
The pressroom site must be unique per source.
class Source
has_many :pressrooms
after_create :create_initial_pressroom! # if pressrooms.empty?
...
end
class Pressroom
belongs_to :source
# source.pressrooms.map(&:site) should have unique elements
validate_on_create :check_unique_site
end
This leads to my problem: My Pressroom.make fails, because it builds a Source, which has no pressrooms, so the after_create callback creates one, and when the Pressroom.make tries to finish up, its site is not unique. I don't want to create two pressrooms when I run Pressroom.make
My attempt to solve this is to make the source association in the pressroom blueprint reference the pressroom. Sort of what Source.create :pressrooms => [Pressroom.new] would do.
Pressroom.blueprint do
source { Source.make :pressrooms => [self] }
site { source.site }
end
Unfortunatly, self is not yet a Pressroom. It's an instance of Machinist::Lathe, so I get an ActiveRecord::AssociationTypeMismatch exception.
I'm a bit of a newbie when it comes to factories and Machinist. I don't want to have to change the business logic, and I want to be able to cleanly make pressrooms with Pressroom.make without making two pressrooms in the process. If switching to factory-girl would help, I'm open to that.
I'd be grateful for any ideas on how to solve this.
Googling around, I found some hints on http://webcrisps.wordpress.com/2009/08/13/stubbing-before_create-callbacks-in-a-machinist-blueprint/ – to stub the after_create :create_initial_pressroom! callback on Source, in the Source blueprint – using Machinist 2 and Mocha here:
Pressroom.blueprint do
source { Source.make!(:without_initial_pressroom) }
site { object.source.site }
end
Source.blueprint do
site
end
Source.blueprint(:without_initial_pressroom) do
object.stubs(:create_initial_pressroom!).returns(true)
end
This way, Pressroom.make! works like it should, Source.make! works like it should, and... I guess I'm happy. But still a bit perplexed by the problems I ran into in the solution I tried above (both in machinist 1 and 2).
If anyone knows how to make this work with object, let me know. It'd be a lot cleaner, and besides, I generally don't like accepting my own answers here on stackoverflow.
Are you using Machinist 1 or 2? These suggestions for Machinist 2 and may or may not work in Machinist 1. I can't remember how you do this in Machinist 1 (and can't be bothered to google!).
To do it in the way you're suggesting, you need to use object:
Pressroom.blueprint do
source { Source.make :pressrooms => [object] }
site { source.site }
end
But a much nicer way to do it is to take advantage of the fact Machinst knows about the models associations and just let it do its thing:
Pressroom.blueprint do
source
site { source.site }
end
Assuming your associations are setup correctly, that should work. See the Blueprints wiki page for more.

What is the default code for bulk has_many :through join assignment in rails?

I have a basic has_many :through relationship that is bi-directional:
calendars have many calendar_calendar_events
calendars have many events through calendar_calendar_events
events have many calendar_calendar_events
events have many calendars through calendar_calendar_events
I'm wanting to assign calendars to an event with the basic calendar_ids= function that has_many :through sets up, however, I want to override this function to add some extra magic. I've had a look through the rails source and can't find the code for this function. I'm wondering if someone could point me to it. I'll then override it for this class to add the stuff that I want :)
You can find the source code in the file lib/active_record/associations.rb at line 1295
def collection_accessor_methods(reflection, association_proxy_class, writer = true)
collection_reader_method(reflection, association_proxy_class)
if writer
define_method("#{reflection.name}=") do |new_value|
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
association = send(reflection.name)
association.replace(new_value)
association
end
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
ids = (new_value || []).reject { |nid| nid.blank? }
send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
end
end
end
You should definitely avoid to overwrite such this method to add magic stuff.
Rails is already "too much magic" sometimes. I would suggest to create a virtual attribute with all your custom logic for several reasons:
some other rails methods might rely on the default implementation
you rely on a specific API that might going to change in future ActiveRecord versions
After a bit of a hunt I found it:
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/collection_accessor_methods
It didn't look like what I thought it would look like, so that's why I probably missed it. I ended up overriding the calendars= method instead of the calendar_ids= method and everything works well.
In response to the answer above, I used alias_method_chain to override the default setter and add my feature. Works quite well, though I'm not sure why I have to send the method setter instead of just using it normally. It didn't seem to work though so this will do :)
def calendars_with_primary_calendar=(new_calendars)
new_calendars << calendar unless new_record?
send('calendars_without_primary_calendar=', new_calendars) # Not sure why we have to call it this way
end
alias_method_chain :calendars=, :primary_calendar

Resources