scope not working on Mongoid (undefined method `to_criteria') - ruby-on-rails-4

I invoke ReleaseSchedule.next_release in other controller
and got the following error
NoMethodError (undefined method `to_criteria' for #<ReleaseSchedule:0x007f9cfafbfe70>):
app/controllers/weekly_query_controller.rb:15:in `next_release'
releae_schedule.rb
class ReleaseSchedule
scope :next_release, ->(){ ReleaseSchedule.where(:release_date.gte => Time.now).without(:_id, :created_at, :updated_at).first }
end

That's not really a scope at all, that's just a class method wrapped up to look like a scope. There are two problems:
You're saying ReleaseSchedule.where(...) so you can't chain the "scope" (i.e. ReleaseSchedule.where(...).next_release won't do what it is supposed to do).
Your "scope" ends in first so it won't return a query, it just returns a single instance.
2 is probably where your NoMethodError comes from.
If you really want it to be a scope for some reason then you'd say:
# No `first` or explicit class reference in here.
scope :next_release, -> { where(:release_date.gte => Time.now).without(:_id, :created_at, :updated_at) }
and use it as:
# The `first` goes here instead.
r = ReleaseSchedule.next_release.first
But really, you just want a class method:
def self.next_release
where(:release_date.gte => Time.now).without(:_id, :created_at, :updated_at).first
end
The scope macro is, after all, just a fancy way to build class methods. The only reason we have scope is to express an intent (i.e. to build queries piece by piece) and what you're doing doesn't match that intent.

Related

Use a method on a StateNotifier Riverpod for changing a bool [duplicate]

In the context of a Flutter 2.0.5 app whose state I'd like to manage with Riverpod, I thought I can declare a StateNotifierProvider like this:
import 'package:flutter_riverpod/flutter_riverpod.dart';
final counterProvider = StateNotifierProvider<CounterStateNotifier>((ref) => CounterStateNotifier());
class CounterStateNotifier extends StateNotifier<int> {
CounterStateNotifier([int count = 0]) : super(count);
void increment() => state++;
}
But Android Studio (and later the Dart compiler as well) complains about the line where I declare the counterProvider variable:
The type 'StateNotifierProvider' is declared with 2 type parameters, but 1 type arguments were given.
Removing the <CounterStateNotifier> type parameter in StateNotifierProvider<CounterStateNotifier> removes the error. However, attempting to read the provider and call its increment method (setting () => context.read(counterProvider).increment() as the onPressed of an ElevatedButton, then pressing the button) gives the following runtime error:
'increment'
method not found
Receiver: 0
Arguments: []
Why is context.read(counterProvider) returning the int state instead of the notifier? And what is the reason behind the type parameter error mentioned in the first part of my question?
I should mention that I'm running my app on the web (with flutter run -d Chrome).
As of Riverpod 0.14.0, State is the default value exposed by StateNotifierProvider.
The syntax for declaring your StateNotifierProvider is now as follows:
final counterProvider = StateNotifierProvider<CounterStateNotifier, int>((ref) => CounterStateNotifier());
Accessing functions now requires adding .notifier (accessing the StateNotifier itself):
context.read(counterProvider.notifier).increment();
And like you've noticed, you now access the state like so:
final count = context.read(counterProvider);
More on the changes here.
You may also use dynamic to accept any type if value for the StateNotifierProvider
final modelProvider =
StateNotifierProvider.autoDispose<ModelClassName, dynamic>(
(ref) => ModelClassName());

How do I access an object's eigenclass in Crystal?

In Ruby, it's possible to access the eigenclass (or "singleton class") of an object by reopening it. This is particularly useful for defining "private class methods":
class Foo
class << self
private
def declarative_method_name
end
end
declarative_method_name
end
# Foo.declarative_method_name => ERROR!
However, in Crystal this is not syntax:
Syntax error in ./test.cr:2: expecting token 'CONST', not '<<'
class << self
^
Is there another (or indeed any) way to achieve this in Crystal currently?
There's no eigenclass, or more commonly called singleton class in Ruby these days (given there's Object#singleton_class), in Crystal.
However defining class methods and calling them on the class level is supported:
class Foo
private def self.declarative_method_name
puts "hey"
end
declarative_method_name
end
https://carc.in/#/r/1316
The def self. construct here is specialized by the compiler and there's no more general concept beneath it, yet.
How would you make a super classes' new method private while still allowing it's subclasses' to be public?
class Foo
private self.new; end
end
class Bar < Foo
end
Bar.new #=> error: private method 'new' called for Foo:Class
It's also worth noting here that unlike in Ruby, class variables don't transcend inheritance. In Ruby the following code has a strange side effect...
class Foo
##var = 'foo'
def var
##var
end
end
class Bar < Foo
##var = 'bar'
end
puts Foo.new.var
It'll return 'bar' despite the fact that we modified the class variable on Bar. In crystal it returns 'foo' meaning that another reason we'd access the eiganclass, to store and read class level state safely, isn't necessary in crystal, we can just use class variables.

How can I use conditions from 2 scopes on 2 models together

I have the following models, which both currently have the same scope:
programme.rb
has_many: :campaigns
scope :visible, -> { where(status: 1) }
camapaign.rb
belongs_to: programme
scope :visible, -> { where(status: 1) }
I would like to edit the campaign scope to only search through the programmes which are visible. Ideally I would like to change that scope to be:
(most likely in a function)
def self.visible
Programme.visible.campaigns.where.(status: 1)
end
But this is returning:
undefined method `campaigns' for #<Programme::ActiveRecord_Relation:0x007f8c9677cd68>
Can someone explain why I can't do this? Also, if any solution to this would be appreciated? preferably without using .joins (But I'll take what I can get at this point)
Thanks
It's hard to tell from your question whether status is an attribute of Programme, Campaign, or both.
Assuming both, you could do this:
# class Programme
has_many :campaigns
scope :visible, -> { where(status: 1) }
# class Campaign
belongs_to :programme
scope :visible, do
joins(:programme).
merge(Programme.visible).
where(status: 1)
end
With this setup, Campaign.visible will only return Campaigns that belong to visible Programmes (and where status == 1, not sure if that means "visible" in your app).
As for why you can't call
Programme.visible.campaigns.where(status: 1)
campaigns is a collection that belongs to a single Programme. It doesn't make sense to ask a collection of Programmes for its campaigns.

Use rspec to test class methods are calling scopes

I have created rspec tests for my scopes (scope1, scope2 and scope3) and they pass as expected but I would also like to add some tests for a class method that I have which is what is actually called from my controller (the controller calls the scopes indirectly via this class method):
def self.my_class_method(arg1, arg2)
scoped = self.all
if arg1.present?
scoped = scoped.scope1(arg1)
end
if arg2.present?
scoped = scoped.scope2(arg2)
elsif arg1.present?
scoped = scoped.scope3(arg1)
end
scoped
end
It seems a bit redundant to run the same scope tests for each scenario in this class method when I know they already pass so I assume I really only need to ensure that different scopes are called/applied dependant on the args being passed into this class method.
Can someone advise on what this rspec test would look like.
I thought it might be something along the lines of
expect_any_instance_of(MyModel.my_class_method(arg1, nil)).to receive(:scope1).with(arg1, nil)
but that doesn't work.
I would also appreciate confirmation that this is all that's necessary to test in this situation when I've already tested the scopes anyway would be reassurring.
The Rspec code you wrote is really testing the internal implementation of your method. You should test that the method returns what you want it to return given the arguments, not that it does it in a certain way. That way, your tests will be less brittle. For example if you change what scope1 is called, you won't have to rewrite your my_class_method tests.
I would do that by creating a number of instances of the class and then call the method with various arguments and check that the results are what you expect.
I don't know what scope1 and scope2 do, so I made an example where the arguments are a name attribute for you model and the scope methods simply retrieve all models except those with that name. Obviously, whatever your real arguments and scope methods do you should put that in your tests, and you should modify the expected results accordingly.
I used the to_ary method for the expected results since the self.all call actually returns an ActiveRecord association and therefore wouldn't otherwise match the expected array. You could probably use includes and does_not_includes instead of eq, but perhaps you care about the order or something.
describe MyModel do
describe ".my_class_method" do
# Could be helpful to use FactoryGirl here
# Also note the bang (!) version of let
let!(:my_model_1) { MyModel.create(name: "alex") }
let!(:my_model_2) { MyModel.create(name: "bob") }
let!(:my_model_3) { MyModel.create(name: "chris") }
context "with nil arguments" do
let(:arg1) { nil }
let(:arg2) { nil }
it "returns all" do
expected = [my_model_1, my_model_2, my_model_3]
expect_my_class_method_to_return expected
end
end
context "with a first argument equal to a model's name" do
let(:arg1) { my_model_1.name }
let(:arg2) { nil }
it "returns all except models with name matching the argument" do
expected = [my_model_2, my_model_3]
expect_my_class_method_to_return expected
end
context "with a second argument equal to another model's name" do
let(:arg1) { my_model_1.name }
let(:arg2) { my_model_2.name }
it "returns all except models with name matching either argument" do
expected = [my_model_3]
expect_my_class_method_to_return expected
end
end
end
end
private
def expect_my_class_method_to_return(expected)
actual = described_class.my_class_method(arg1, arg2).to_ary
expect(actual).to eq expected
end
end

Error: Non-assignable model expression: undefined

I'm trying to write tests for a directive, so I'm constructing it by hand with $compile. (Yes, I know that I can use e2e tests for this, but in this case I want to test some DOM independent logic so I'm using the unit test approach.)
I have a field that I'm binding to with scope: {foo: '='}. When I try to set it on the directive's scope, I get the following error:
Error: Non-assignable model expression: undefined (directive: foo)
at Error (<anonymous>)
at $get.parentSet (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js:4146:25)
at Object.<anonymous> (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js:4160:23)
at Object.$get.Scope.$digest (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js:7693:38)
at Object.$get.Scope.$apply (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js:7894:24)
at SNAKE_CASE_REGEXP (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js:930:13)
at Object.invoke (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js:2788:25)
at bootstrap (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js:928:12)
at angularInit (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js:904:5)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js:14397:5
Reproduced in this fiddle.
In your directive you have:
scope: {
bar: '='
}
This means it's going to look on the directive element (<div foo></div>) for an attribute name "bar" and do a two-way databinding within your directive and the object specified for the attribute. So, first you need to add a bar attribute, and then you need to have something set in your scope with the name of the value of the attribute. See http://jsfiddle.net/u4BTu/6/
Another possible cause of this error is neglecting to account for Angular's normalization of the attributes. For instance, if you have the following directive:
<my-directive parameter-one="foo" parameter_two="bar"></my-directive>
With this scope declaration:
scope: {
'parameter-one': '=',
parameter_two: '=',
}
You'll get this error. To correct, use the normalized versions for the scope declaration:
scope: {
parameterOne: '=',
parameterTwo: '=',
}