Rails Netzke v0.10.1 - Refreshing grid panel within a tabpanel - ruby-on-rails

I try to make a Netzke component with one master grid and subgrids in the south region of a Panel.
When a row in the maingrid is selected then should the subgrids be filtered with records related to the record in maingrid - like described here for an old netzke version:
https://groups.google.com/forum/#!searchin/netzke/tabpanel/netzke/PFAQ-wYyNog/2RJgRLzh80oJ
I know that netzke is not further in development but I use it in a project.
ruby 2.1.2 (Mac OSX rbenv)
rails 4.0.10
netzke-core v0.10.1
netzke-basepack v0.10.1
Here my Code:
models:
class MbOrganisation < ActiveRecord::Base
has_many :mb_contacts
def customer_name
"#{orga_customer} - #{orga_name1}"
end
end
class MbContact < ActiveRecord::Base
belongs_to :mb_organisation
end
This is the central component
app/components/organisation_multitab.rb
class OrganisationMultitab < Netzke::Base
component :organisation_organisations
component :organisation_tabpanel do |c|
c.klass = MblixBaseTabpanel
c.items = [:organisation_contacts]
end
js_configure do |c|
c.layout = :border
c.border = false
c.init_component = <<-JS
function(){
// calling superclass's initComponent
this.callParent();
// setting the 'rowclick' event
var view = this.netzkeGetComponent('organisation_organisations').getView();
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectItem({item_id: record.get('id')});
}, this);
}
JS
end
def configure(c)
super
c.items = [
{ items: [:organisation_organisations], region: :center },
{ items: [:organisation_tabpanel], region: :south, height: 200, split: true }
]
end
endpoint :select_item do |params, this|
# store selected id in the session for this component's instance
component_session[:selected_item_id] = params[:item_id]
end
end
These components are additionally used
Maingrid - organisation_organisations.rb
class OrganisationOrganisations < Netzke::Basepack::Grid
def configure(c)
super
c.model = "MbOrganisation"
c.columns = [:orga_customer, :orga_name1, :orga_name2, :orga_street, :orga_zip, :orga_city, :orga_tel, :orga_email]
c.force_fit = true
end
end
Component with Tabpanel- base_tabpanel.rb:
class BaseTabpanel < Netzke::Basepack::TabPanel
component :organisation_contacts do |c|
c.data_store = {auto_load: false}
c.scope = {:mb_organisation_id => component_session[:selected_item_id]}
c.strong_default_attrs = {:mb_organisation_id => component_session[:selected_item_id]}
end
def configure(c)
super
c.active_tab = 0
c.prevent_header = true
end
end
The grid component for the contacts:
class OrganisationContacts < Netzke::Basepack::Grid
def configure(c)
super
c.model = "MbContact"
c.columns = [{ :name => :mb_organisation__customer_name,
:header => "Organisation"
}, :cont_salutation, :cont_title, :cont_lastname, :cont_firstname, :cont_email, :cont_tel, :cont_mobile, :cont_birthday]
c.force_fit = true
end
end
The function this.selectItem(...) is correct triggered and calls the endpoint in OrganisationMultitab.
I have two problems/questions
First
- How can I automatically reload the stores of the subgrids in the tabpanel?
The described way in the linked google groups article: https://groups.google.com/forum/#!searchin/netzke/tabpanel/netzke/PFAQ-wYyNog/2RJgRLzh80oJ is outdated (It's for netzke v0.5 - I use netzke v0.10.1):
{
:south => {
:item0 => {:load_store_data => aggregatee_instance(:south__item0).get_data},
:item1 => {:load_store_data => aggregatee_instance(:south__item1).get_data}
}
}
second problem: I got an error - when I manually refresh the subgrids:
ActiveModel::ForbiddenAttributesError in NetzkeController#direct
Update
The ActiveModel::ForbiddenAttributesError is solved by myself. There was a bug in the netzke-basepack gem:
Netzke::Basepack::Grid ran in an ActiveModel::ForbiddenAttributesError (rails 4 strong parameters) when the component, like above described, has a scope configured. (config[:scope] will later be merged to the params object that is an ActionController::Parameters object. - As the scope is database related this will be denied with ActiveModel::ForbiddenAttributesError )
My solution: In the endpoint.rb the ActionController::Parameters will be converted to a Hash - then the error is gone.
I made a fork and a pull request in github for this gem.
But
the second problem is not solved.
second problem: Now the subgrids can be manually refreshed without an error but they are always empty.
I guess the scope in the child component
component :organisation_contacts do |c|
c.data_store = {auto_load: false}
c.scope = {:mb_organisation_id => component_session[:selected_item_id]}
c. strong_default_attrs = {:mb_organisation_id => component_session[:selected_item_id]}
end
has no access to the value of the
component_session[:selected_item_id]
of the Organisation MultiTab parent component?
But it is neccessary to split the components - like described here: https://groups.google.com/forum/#!searchin/netzke/tabpanel/netzke/sDrU7NZIlqg/-2wGmed7fjcJ
Hope there is somebody who can help me. :-)
Thanks
Best regards

You're getting the ActiveModel::ForbiddenAttributesError because you're not permiting the attributes from the controller. Rails now uses strong_parameters instead of attr_accessible (like in Rails 3).

So I found the solution by my self.
First issue - reloading the grids in the Tabs
The store of the Ext gridcomponent can also be accessed in the Javascript.
So I extended the Javascript configuration of the OrganisationMulitab with this part:
Ext.each(this.netzkeGetComponent('organisation_tabpanel').items.items, function(item, index) {
item.getStore().load();
});
Second issue - send the selected id to the scope in the child component
The value must be sent to the session of the child component - so this does the job:
component_instance(:organisation_tabpanel).component_session[:selected_item_id] = params[:item_id]
instead of
component_session[:selected_item_id] = params[:item_id]
(The problem with the ActiveModel::ForbiddenAttributesError was a bug in the gem - solution is in my update of the question - I made a fork of the gem https://github.com/tomg65/netzke-basepack/tree/master-fixes-changes and sent a pull request to the original https://github.com/netzke/netzke-basepack/pull/158)
So the final code looks like this and all works fine:
class OrganisationMultitab < Netzke::Base
component :organisation_organisations
component :organisation_tabpanel do |c|
c.klass = MblixBaseTabpanel
c.items = [:organisation_contacts]
end
js_configure do |c|
c.layout = :border
c.border = false
c.init_component = <<-JS
function(){
// calling superclass's initComponent
this.callParent();
// setting the 'rowclick' event
var view = this.netzkeGetComponent('organisation_organisations').getView();
view.on('itemclick', function(view, record){
// The beauty of using Ext.Direct: calling 3 endpoints in a row, which results in a single call to the server!
this.selectItem({item_id: record.get('id')});
Ext.each(this.netzkeGetComponent('organisation_tabpanel').items.items, function(item, index) {
item.getStore().load();
});
}, this);
}
JS
end
def configure(c)
super
c.items = [
{ items: [:organisation_organisations], region: :center },
{ items: [:organisation_tabpanel], region: :south, height: 200, split: true }
]
end
endpoint :select_item do |params, this|
# store selected id in the session for child component's instance
component_instance(:organisation_tabpanel).component_session[:selected_item_id] = params[:item_id]
end
end
Hope this helps others too.
Best regards
Thomas

Related

Mongoid Aggregate result into an instance of a rails model

Introduction
Correcting a legacy code, there is an index of object LandingPage where most columns are supposed to be sortable, but aren't. This was mostly corrected, but few columns keep posing me trouble.
Theses columns are the one needing an aggregation, because based on a count of other documents. To simplify the explanation of the problem, I will speak only about one of them which is called Visit, as the rest of the code will just be duplication.
The code fetch sorted and paginate data, then modify each object using LandingPage methods before sending the json back. It was already like this and I can't modify it.
Because of that, I need to do an aggregation (to sort LandingPage by Visit counts), then get the object as LandingPage instance to let the legacy code work on them.
The problem is the incapacity to transform Mongoid::Document to a LandingPage instance
Here is the error I got:
Mongoid::Errors::UnknownAttribute:
Message:
unknown_attribute : message
Summary:
unknown_attribute : summary
Resolution:
unknown_attribute : resolution
Here is my code:
def controller_function
landing_pages = fetch_landing_page
landing_page_hash[:data] = landing_pages.map do |landing_page|
landing_page.do_something
# Do other things
end
render json: landing_page_hash
end
def fetch_landing_page
criteria = LandingPage.where(archived: false)
columns_name = params[:columns_name]
column_direction = params[:column_direction]
case order_column_name
when 'visit'
order_by_visits(criteria, column_direction)
else
criteria.order_by(columns_name => column_direction).paginate(
per_page: params[:length],
page: (params[:start].to_i / params[:length].to_i) + 1
)
end
def order_by_visit(criteria, order_direction)
def order_by_visits(landing_pages, column_direction)
LandingPage.collection.aggregate([
{ '$match': landing_pages.selector },
{ '$lookup': {
from: 'visits',
localField: '_id',
foreignField: 'landing_page_id',
as: 'visits'
}},
{ '$addFields': { 'visits_count': { '$size': '$visits' }}},
{ '$sort': { 'visits_count': column_direction == 'asc' ? 1 : -1 }},
{ '$unset': ['visits', 'visits_count'] },
{ '$skip': params[:start].to_i },
{ '$limit': params[:length].to_i }
]).map { |attrs| LandingPage.new(attrs) { |o| o.new_record = false } }
end
end
What I have tried
Copy and past the hash in console to LandingPage.new(attributes), and the instance was created and valid.
Change the attributes key from string to symbole, and it still didn't work.
Using is_a?(hash) on any element of the returned array returns true.
Put it to json and then back to a hash. Still got a Mongoid::Document.
How can I make the return of the Aggregate be a valid instance of LandingPage ?
Aggregation pipeline is implemented by the Ruby MongoDB driver, not by Mongoid, and as such does not return Mongoid model instances.
An example of how one might obtain Mongoid model instances is given in documentation.

Create a record to the database without a form in Ruby on Rails 5

In my controller I have defined a method that I want to save to my database automatically without a form.
This is what I have so far, but nothing is being saved to the database.
Here's the method
def recommended_recipes
#today = Date.today
#recRecipe = RecommendedRecipe.where(user_id: current_user, day: #today)
if #recRecipe.blank?
response = RestClient.get("https://spoonacular-recipe-food-nutrition-v1.p.mashape.com/recipes/mealplans/generate?targetCalories=3000&timeFrame=day", headers={"X-Mashape-Key" => "",
"Accept" => "application/json"})
#parsedResponse = JSON.parse(response)
#recRecipes = #parsedResponse['meals']
#recRecipesNutrients = #parsedResponse['nutrients']
#totalCalories = #recRecipesNutrients['calories']
#totalProteins = #recRecipesNutrients['protein']
#totalFat = #recRecipesNutrients['fat']
#totalCarbohydrates = #recRecipesNutrients['carbohydrates']
#newRecRecipe = RecommendedRecipe.create(meals_response: #recRecipes, total_calories: #totalCalories, total_proteins: #totalProteins, total_fat: #totalFat, total_carbohydrates: #totalCarbohydrates, day: #today, user_id: current_user)
end
end
I want to save the #newRecipe to my database called recommended_recipes whenever the method is called.
How can I make a record in the database?
Thanks in advance!
After hours of kicking myself in the head I did this in the model:
class RecommendedRecipe < ApplicationRecord
belongs_to :user, optional: true
end
I added the optional: true

Rails Admin routes inside config

I'm using Rails Admin for my admin area.
The sidebar panel should have some links to the instances of a model.
In rails_admin.rb I've tried something like:
RailsAdmin.config do |config|
#navigation_links = Hash[*Conference.all.map {|conference| [conference.name, bindings[:view].main_app.show_path(model_name: 'conference', id: conference.id)]}.flatten]
config.navigation_static_links = #navigation_links
end
However, here I do not have access to bindings. So, how can I get the url of an admin resource here? I cannot see it in the documentation
Thanks
My answer probably not what you want to do, but it can be helpful.
I've checked rails_admin.gem and i found that there are two methods that responsible for rendering sidebar menu.
def main_navigation
nodes_stack = RailsAdmin::Config.visible_models(controller: controller)
node_model_names = nodes_stack.collect { |c| c.abstract_model.model_name }
nodes_stack.group_by(&:navigation_label).collect do |navigation_label, nodes|
nodes = nodes.select { |n| n.parent.nil? || !n.parent.to_s.in?(node_model_names) }
li_stack = navigation nodes_stack, nodes
label = navigation_label || t('admin.misc.navigation')
%(<li class='dropdown-header'>#{capitalize_first_letter label}</li>#{li_stack}) if li_stack.present?
end.join.html_safe
end
Method above responsible for getting list of models, especially:
nodes_stack = RailsAdmin::Config.visible_models(controller: controller)
Second method responsible for rendering item in the menu (aka li):
def navigation(nodes_stack, nodes, level = 0)
nodes.collect do |node|
model_param = node.abstract_model.to_param
url = url_for(action: :index, controller: 'rails_admin/main', model_name: model_param)
level_class = " nav-level-#{level}" if level > 0
nav_icon = node.navigation_icon ? %(<i class="#{node.navigation_icon}"></i>).html_safe : ''
li = content_tag :li, data: {model: model_param} do
link_to nav_icon + capitalize_first_letter(node.label_plural), url, class: "pjax#{level_class}"
end
li + navigation(nodes_stack, nodes_stack.select { |n| n.parent.to_s == node.abstract_model.model_name }, level + 1)
end.join.html_safe
end
So you can patch this methods to get what you need.
module RailsAdmin
module ApplicationHelper
def main_navigation
# your code
end
end
end
rails_admin.gem module
P.S. I love what you can read from rails doctrine about monkey patching:
This power has frequently been derided as simply too much for mere
mortal programmers to handle.

Create or Update Rails 4 - updates but also creates (Refactoring)

In my Rails API I have the following code in my Child model:
before_create :delete_error_from_values, :check_errors, :update_child_if_exists
def delete_error_from_values
#new_error = self.values["error"]
#values = self.values.tap { |hs| hs.delete("error") }
end
def update_child_if_exists
conditions = {type: self.type, parent_id: self.parent_id}
if existing_child = Child.find_by(conditions)
new_values = existing_child.values.reverse_merge!(#values)
hash = {:values => new_values}
existing_child.update_attributes(hash)
end
end
def check_errors
if self.type == "error"
conditions = {type: self.type, parent_id: self.parent_id}
if existing_child = Child.find_by(conditions)
bd_errors = existing_child.error_summary
bd_errors[#new_error] = bd_errors[#new_error].to_i + 1
hash = {:error_summary => bd_errors}
existing_child.update_attributes(hash)
else
self.error_summary = {#new_error => 1}
end
end
end
This works like expected, except for one small detail: The Child is updated if a record by type and parent_id already exists, but it is also created. How can I refactor this to stop creation?
I've tried to include return false, but if I do this, the update is not successful.
I wish to have something like find_or_create_by, but I'm not sure how to use it for this cases.
May be you can refactor your code in following approach:
def create
#parent = Parent.find(params[:parent_id])
existing_child = Child.where(type: child_params[:type], parent_id:
child_params[:parent_id]).first
if existing_child.present?
existing_child.update_attributes(attribute: value_1)
else
#child = #parent.child.build(child_params)
end
#other saving related code goes here.
end
This is just a basic piece of example.
Try creating separate instance methods to keep the Contrller DRY. :)

Using a method while looping through an array in ruby

I am using ruby-aaws to return Amazon Products and I want to enter them into my DB. I have created a model Amazonproduct and I have created a method get_amazon_data to return an array with all the product information. When i define the specific element in the array ( e.g. to_a[0] ) and then use ruby-aaws item_attributes method, it returns the name I am searching for and saves it to my DB. I am trying to iterate through the array and still have the item_attributes method work. When i don't define the element, i get this error: undefined method `item_attributes' for #Array:0x7f012cae2d68
Here is the code in my controller.
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each { |name|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = #arr.item_attributes.title.to_s
}
EDIT: Code in my model to see if that helps:
class Amazonproduct < ActiveRecord::Base
def self.get_amazon_data(r)
resp = Amazon::AWS.item_search('GourmetFood', { 'Keywords' => 'Coffee Maker' })
items = resp.item_search_response.items.item
end
end
Thanks for any help/advice.
I'm not familiar with the Amazon API, but I do observe that #arr is an array. Arrays do not usually have methods like item_attributes, so you probably lost track of which object was which somewhere in the coding process. It happens ;)
Try moving that .item_attributes call onto the object that supports that method. Maybe amazonproduct.get_amazon_data(:r), before its being turned into an array with to_a, has that method?
It's not quite clear to me what your classes are doing but to use #each, you can do something like
hash = {}
[['name', 'Macbook'], ['price', 1000]].each do |sub_array|
hash[sub_array[0]] = sub_array[1]
end
which gives you a hash like
{ 'name' => 'Macbook', 'price' => 1000 }
This hash may be easier to work with
#product = Product.new
#product.name = hash[:name]
....
EDIT
Try
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each do |aws_object|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = aws_object.item_attributes.title.to_s
end
end

Resources