I am not able to access an instance variable of the outer class in the inner class. Its a simple swing app that i am creating using JRuby:
class MainApp
def initialize
...
#textArea = Swing::JTextArea.new
#button = Swing::JButton.new
#button.addActionListener(ButtonListener.new)
...
end
class ButtonListener
def actionPerformed(e)
puts #textArea.getText #cant do this
end
end
end
The only workaround i can think of is this:
...
#button.addActionListener(ButtonListener.new(#textArea))
...
class ButtonListener
def initialize(control)
#swingcontrol = control
end
end
and then use the #swingcontrol ins place of #textArea in the 'actionPerformed' method.
I guess it's not possible to directly access the outer class members from the inner class without resorting to hacks. Because #textArea in the ButtonListener class is different from #textArea in the MainApp.
(I'm new to ruby, so I could be wrong about this. So, feel free to correct me)
The Ruby way to do this is to use a block rather than a nested class.
class MainApp
def initialize
...
#textArea = Swing::JTextArea.new
#button = Swing::JButton.new
#button.addActionListener do |e|
puts #textArea.getText
end
...
end
end
Related
I'm trying to make a Jruby app with a TableView but I haven't been able to populate the table with data or even find some sample code to do so. Here's the relevant part of my fxml:
<TableView prefHeight="400.0" prefWidth="200.0" id="table">
<columns>
<TableColumn prefWidth="75.0" text="name">
<cellValueFactory>
<PropertyValueFactory property="name" />
</cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="address">
<cellValueFactory>
<PropertyValueFactory property="address" />
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
And here's the relevant ruby code:
class Person
attr_accessor :name, :address
def initialize
#name = 'foo'
#address = 'bar'
end
end
class HelloWorldApp < JRubyFX::Application
def start(stage)
with(stage, title: "Hello World!", width: 800, height: 600) do
fxml HelloWorldController
#data = observable_array_list
#data.add Person.new
stage['#table'].set_items #data
show
end
end
end
Can someone suggest what I'm doing wrong or point me to a working sample code?
See the contrib/fxmltableview sample; I think that is exactly what you want to do. The issue you are running into is the fact that PropertyValueFactory is a Java class, and it is trying to access a Person which is a JRuby class. By default this won't work as this question shows, but you can easily fix that by calling Person.become_java!. However, even if you do that, it won't work as the PropertyValueFactory expects getter methods of the form [javatype] get[PropertyName]() whereas attr_accessor only generates getter methods of the form [rubytype] [propertyname](). To solve this, use fxml_accessor instead, which generates the proper methods (but doesn't use # vars, those are the raw property instances):
class Person
include JRubyFX # gain access to fxml_accessor
# must specify type as the concrete `Property` implementation
fxml_accessor :name, SimpleStringProperty
fxml_accessor :address, SimpleStringProperty
def initialize
# note use of self to call the method Person#name= instead of creating local variable
self.name = 'foo'
self.address = 'bar'
# could also technically use #address.setValue('bar'), but that isn't as rubyish
end
end
# become_java! is needed whenever you pass JRuby objects to java classes
# that try to call methods on them, such as this case. If you used your own
# cellValueFactory, this probably would not be necessary on person, but still
# needed on your CVF
Person.become_java!
Lets assume I have the following model:
class Computer < ActiveRecord::Base
end
and I retrieve all computers in my controllers like this:
#computers = Computer.all
Now I add a feature to deactivate certain computers and filter the deactivated computer like this:
class Computer < ActiveRecord::Base
scope :not_deactivated, -> { where('deactivated IS NULL OR deactivated = ?', false) }
end
and in my controllers:
#computers = Computer.all.not_deactivated
The issue with this method is that I have to add not_deactivated in all my controllers.
Is it possible to do this filter in the model so I don't have to touch the controllers?
Easy and common thing to do in controller:
before_action :not_deactivated # , only [:index...]
private
def not_deactivated
#computers = Computer.where(your code)
end
Since the controller handles the view you must initiate the object somehow anyway. By filtering like that you can achieve what you are trying to do now with the model filter.
you can do it by declaring default_scope to run this every time you make a call to your Computer model
class Computer < ActiveRecord::Base
default_scope { where('deactivated IS NULL OR deactivated = ?', false) }
end
So, when you run Computer.all the result you get is Computer.where('deactivated IS NULL OR deactivated = ?', false)
I'm doing some test with Sinatra v1.4.4 and Active Record v4.0.2. I've created a DBase and a table named Company with Mysql Workbench. In table Company there are two fields lat & long of DECIMAL(10,8) and DECIMAL(11,8) type respectively. Without using migrations I defined the Company model as follow:
class Company < ActiveRecord::Base
end
Everything works except the fact that lat and lng are served as string and not as float/decimal. Is there any way to define the type in the above Class Company definition. Here you can find the Sinatra route serving the JSON response:
get '/companies/:companyId' do |companyId|
begin
gotCompany = Company.find(companyId)
[200, {'Content-Type' => 'application/json'}, [{code:200, company: gotCompany.attributes, message: t.company.found}.to_json]]
rescue
[404, {'Content-Type' => 'application/json'}, [{code:404, message:t.company.not_found}.to_json]]
end
end
Active Record correctly recognize them as decimal. For example, executing this code:
Company.columns.each {|c| puts c.type}
Maybe its the Active Record object attributes method typecast?
Thanks,
Luca
You can wrap the getter methods for those attributes and cast them:
class Company < ActiveRecord::Base
def lat
read_attribute(:lat).to_f
end
def lng
read_attribute(:lng).to_f
end
end
That will convert them to floats, e.g:
"1.61803399".to_f
=> 1.61803399
Edit:
Want a more declarative way? Just extend ActiveRecord::Base:
# config/initializers/ar_type_casting.rb
class ActiveRecord::Base
def self.cast_attribute(attribute, type_cast)
define_method attribute do
val = read_attribute(attribute)
val.respond_to?(type_cast) ? val.send(type_cast) : val
end
end
end
Then use it like this:
class Company < ActiveRecord::Base
cast_attribute :lat, :to_f
cast_attribute :lng, :to_f
end
Now when you call those methods on an instance they will be type casted to_f.
Following diego.greyrobot reply I modified my Company class with an additional method. It overrides the attributes method and afterwards typecast the needed fields. Yet something more declarative would be desirable imho.
class Company < ActiveRecord::Base
def attributes
retHash = super
retHash['lat'] = self.lat.to_f
retHash['lng'] = self.lng.to_f
retHash
end
end
I'm rendering a model and it's children Books in JSON like so:
{"id":2,"complete":false,"private":false, "books" [{ "id":2,"name":"Some Book"},.....
I then come to update this model by passing the same JSON back to my controller and I get the following error:
ActiveRecord::AssociationTypeMismatch (Book (#2245089560) expected, got ActionController::Parameters(#2153445460))
In my controller I'm using the following to update:
#project.update_attributes!(project_params)
private
def project_params
params.permit(:id, { books: [:id] } )
end
No matter which attributes I whitelist in permit I can't seem to save the child model.
Am I missing something obvious?
Update - another example:
Controller:
def create
#model = Model.new(model_params)
end
def model_params
params.fetch(:model, {}).permit(:child_model => [:name, :other])
end
Request:
post 'api.address/model', :model => { :child_model => { :name => "some name" } }
Model:
accepts_nested_attributes_for :child_model
Error:
expected ChildModel, got ActionController::Parameters
Tried this method to no avail: http://www.rubyexperiments.com/using-strong-parameters-with-nested-forms/
Are you using accepts_nested_attributes_for :books on your project model? If so, instead of "books", the key should be "books_attributes".
def project_params
params.permit(:id, :complete, :false, :private, books_attributes: [:id, :name])
end
I'm using Angular.js & Rails & Rails serializer, and this worked for me:
Model:
has_many :features
accepts_nested_attributes_for :features
ModelSerializer:
has_many :features, root: :features_attributes
Controller:
params.permit features_attributes: [:id, :enabled]
AngularJS:
ng-repeat="feature in model.features_attributes track by feature.id
My solution to this using ember.js was setting the books_attributes mannualy.
In controller:
def project_params
params[:project][:books_attributes] = params[:project][:books_or_whatever_name_relationships_have] if params[:project][:books_or_whatever_name_relationships_have]
params.require(:project).permit(:attr1, :attr2,...., books_attributes: [:book_attr1, :book_attr2, ....])
end
So rails checks and filters the nested attributes as it expected them to come
This worked for me. My parent model was an Artist and the child model was a Url.
class ArtistsController < ApplicationController
def update
artist = Artist.find(params[:id].to_i)
artist.update_attributes(artist_params)
render json: artist
end
private
def artist_params
remap_urls(params.permit(:name, :description, urls: [:id, :url, :title, :_destroy]))
end
def remap_urls(hash)
urls = hash[:urls]
return hash unless urls
hash.reject{|k,v| k == 'urls' }.merge(:urls_attributes => urls)
end
end
class Artist < ActiveRecord::Base
has_many :urls, dependent: :destroy
accepts_nested_attributes_for :urls, allow_destroy: true
end
class Url < ActiveRecord::Base
belongs_to :artist
end
... and in coffeescript (to handle deletions):
#ArtistCtrl = ($scope, $routeParams, $location, API) ->
$scope.destroyUrls = []
$scope.update = (artist) ->
artist.urls.push({id: id, _destroy: true}) for id in $scope.destroyUrls
artist.$update(redirectToShow, artistError)
$scope.deleteURL = (artist,url) ->
artist.urls.splice(artist.urls.indexOf(url),1)
$scope.destroyUrls.push(url.id)
Something is missing from all of the answers, which is the inputs for fields_for in the form.
The form works if you do this:
f.fields_for #model.submodel do ..
However, the form is sent as model[submodel], but that's what causes the error others have mentioned in their answers. If you try to do model.update(model_params), Rails will raise an error that it's expecting a Submodel type.
To fix this, make sure you follow the :name, value format:
f.fields_for :submodel, #model.submodel do ...
Then in the controller, make sure you put _attributes on your params:
def model_params
params.require(:model).permit(submodel_attributes: [:field])
end
Now the save, update, etc. will work fine.
Wasted several days trying to figure out how to use accepts_nested_attributes with Angular, and the issue is always the same: Rails whitelist will not allow the variables into the params hash. I've tried every single different whitelisting syntax that everyone said on SO and other blogs, tried using :inverse, tried using habtm and mas_many_through, tried manually rolling my own solution but that wont work if the whitelist wont allow params through, tried doing what http://guides.rubyonrails.org says about 'Outside the Scope of Strong Parameters', tried removing whitelisting all together which isnt really an option but it causes other problems anyways. Not sure why rails 4 strong parameter whitelisting wont allow arbitrary data thru, thats a huge problem especially if accepts_nested_attributes doesn't work either.... I guess we are left to just create/delete all associations on a separate page/form/controller and look like an idiot making my end users use several forms/pages to do something that should be easily doable on 1 page with 1 form. Ya know, usually I expect Angular to screw me, but this time Angular worked quite well and it was actually Rails 4 that screwed me twice on 1 issue that should be very straightforward.
I have the following code in my controller:
class TestController < ApplicationController
##a = 1
def index
#temp = connection.execute("select test_id from mastertest limit #{##a}, 5;")
end
And I have the following code in my View(Html.erb) File:
<button type="submit" value="Next" form="submit_form">NEXT</button>
<form id="submit_form">
<% ##a = ##a + 1 %>
<table>
<% #temp.each do |row| %>
<tr><td><%= row[0] %></td></tr>
<% end %>
</table>
</form>
So basically I am trying to change the value of the class variable ##a on clicking the Next button. But it does not change the value of ##aa. Can someone help me how to do that.
Did you try using helper method?
module ApplicationHelper
##a = 1
def increment_a
##a = ##a + 1
end
end
and in your view just call;
<% increment_a %>
Not that the ## variable is a class variable and it's shared among all instances of the that class. So define that class somewhere in the ApplicationHelper class and then it will be shared and can be accessed in the Controllers and views.
In all cases I highly discourage using class variables in such a way and recommend that you ind another way to share data/variables between view / controller. Maybe use another supporting class or store values in the database.
If you want to alter a Rails variable on a form submission, you should put the code to do it in the action which processes the form.
As you've written it, I believe the variable will get set when the template containing the form is rendered.
I also vaguely recall that there's some special considerations about class variables in Rails apps. You should look into that and make sure you're using a technique that won't cause any unexpected results.
Ok I managed to fix this:
Ruby has something called a global variable which can be declared like this :
$a = 1
Using $a everywhere retains its value in the controller and the view as well.